Grand Central Dispatch(GCD):概要、语法和最佳实践

Queue and A

苹果最初是这样描述Grand Central Dispatch (GCD) 的:
1.线程学习成本比较高
2.使用GCD使它简单有趣

以上两个陈述是正确的;以下是一些附加要点:
1.GCD不是一个threads的一个库或者一个包
2.开发者不会直接使用thread的API,使用的是GCD封装好的函数,比较简单
3.GCD是通过FIFO队列实现的并发
4.GCD只是libdispatch名字:#include <dispatch/dispatch.h>

向队列提交Blocks

使用GCD的主要机制是向队列提交Blocks或响应队列中出现的事件。就是这样。有不同的提交方式和许多种类的队列,其中有些很吸引人。最终。您只是在计划要执行的任务或执行任务以响应事件。
神奇的部分是, 并发方面是为您处理。线程管理是对系统负载的自动调整。然而无论如何使用并发都是一个危险操作: 所有 UI 必须在主队列上完成, 如果想查看特定的 NS 或 UI 绘制是否是线程安全的。可以查看Apple官方文档或者google。

这篇文章重点讲的是“向队列提交Blocks”,但是使用者应该知道libdispatch还有更多东西制得深入。

- Dispatch Groups        // coordinate groups of queues
- Semaphores             // traditional counting Semaphores
- Barriers               // synchronize tasks in a given concurrent queue
- Dispatch Sources       // event handling for low-level events
- Dispatch I/O           // file descriptor–based operations
- Dispatch Data Buffers  // memory-based data buffer

创建或者使用 Queues

值得重复的是:使用GCD的主要机制是向队列提交任务。
首先认识到队列的最佳方法在通常的情况下, 只有两种类型的队列: 串行和并发。
Serial queues是单一的,但是不受约束的。如果将一组任务分配给每个串行队列, 它将一次运行一次, 每次只使用一个线程。未提交的方面是串行队列可以切换到任务之间的不同线程。串行队列总是等待任务在完成下一个任务之前完成。因此,任务按FIFO顺序完成。您可以根据需要使用 dispatch_queue_create来制作尽可能多的串行队列。

main queue是一个特殊的串行队列。与其他串行队列不同。这是不受约束的,因为他们正在“约会”许多线程,但只有一个线程,主队列是“已婚”的主线程,所有任务都是在它上面执行的。主队列上的工作需要与运行循环进行良好的操作,这样小operations就不会阻塞UI和其他重要后台服务。与所有串行队列一样, 任务以 FIFO 顺序完成。你可以通过dispatch_get_main_queue得到主线程队列。

如果串行队列是一夫一妻制,那么并发队列是鱼龙混杂的。他们将提交任务到任何可用的线程,甚至新线程取决于系统负载。它们可以在不同的线程上同时执行多个任务。提交给全局队列的任务是线程安全的,并且将副作用减到最小是很重要的。在FIFO命令中提交任务以执行,但不能保证完成顺序。

在Mac OS X 10.6和IOS 4中,只有三个内置的(全局的)并发队列,并且不能生成它们,只能用dispatch_get_global_queue队列获取它们。对于Mac OS 10.7和IOS 5,可以用dispatch_queue_create("label", DISPATCH_QUEUE_CONCURRENT)来创建它们。您无法设置您自己创建的并发队列的优先级。通常, 使用具有适当优先级的全局并发队列比自行制作更有意义。
此处总结用于创建或获取队列的主要函数:

dispatch_queue_create       // create a serial or concurrent queue
dispatch_get_main_queue     // get the one and only main queue
dispatch_get_global_queue   // get one of the global concurrent queues
dispatch_get_current_queue  // DEPRECATED

dispatch_queue_get_label    // get the label of a given queue

关于 dispatch_get_current_queue的快速说明:它被贬低,它也不总是在每一种情况下工作。如果实现需要此项, 则应重构您的实现。他最常见的用例是 "在我运行的任何队列上运行某个block"。设计的重构应将显式目标队列与块作为参数或参数一起传递, 而不是试图依赖于运行时来确定要提交到哪个队列。

将任务添加到队列中

一旦有了自己的队列,就可以通过向它们添加任务来使它们有用。
只要的方法如下:

// Asynchronous functions
dispatch_async
dispatch_after
dispatch_apply
// Synchronous functions
dispatch_once
dispatch_sync

dispatch_async 将向队列提交任务并返回。dispatch_after立即返回,但延迟到指定的时间提交任务。dispatch_apply也立即返回并多次提交任务。
dispatch_sync将任务提交到队列,并仅在任务完成时返回。dispatch_once将在应用程序生命周期内一次性提交任务,当块完成时返回。
事实上,我发现我自己使用dispatch_async, dispatch_afterdispatch_once的频率更频繁一些。
示例代码:

// add ui_update_block to the main queue
dispatch_async(dispatch_get_main_queue(), ui_update_block);

// add check_for_updates_block to some_queue in 2 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), some_queue, check_for_updates_block);

// add work_unit_block to some_queue I times.
dispatch_apply(i, some_queue, work_unit_block);

// perform the only_once_block once and only once. 
static dispatch_once_t onceToken = 0; // It is important this is static!  
// wait for completion
dispatch_once(&onceToken, only_once_block);

// add blocking_block to background_queue & wait for completion
dispatch_sync(background_queue, blocking_block);

队列内存管理

GCD 第一次被苹果爸爸提及是在Mac OS X 10.6 和 iOS 4。在那时GCD objects (queues, semaphores, barriers, etc.) 被设计的很像CFObjects和要求你按照正常的创建规则调用dispatch_releasedispatch_retain
对于Mac OS X 10.8和IOS 6,GCD对象由ARC管理,并且这样的手动引用计数被明确禁止。
此外,在ARC下应用以下警告:

1.如果在GCD对象使用的块中使用GCD对象,则需要处理循环引用问题。使用__weak或显式破坏对象(通过诸如dispatch_source_cancel)机制是很好的方法。从 Xcode 4.6, 静态分析器无法捕捉到此。例子:

// Create a GCD object:
dispatch_queue_t someQueue = dispatch_queue_create("someQueue", nil);
// put a block on the queue, the queue retains the block.
dispatch_async(someQueue, ^{
    // capture the GCD object inside the block,
    // the block retains the queue and BAM! retain cycle!
    const char *label = dispatch_queue_get_label(someQueue);
    NSLog(@"%s", label);
});

// You can use the typical __weak dance to workaround:
__weak dispatch_queue_t weakQueue = someQueue;
dispatch_async(someQueue, ^{
    __strong dispatch_queue_t strongQueue = weakQueue;
    const char *label = dispatch_queue_get_label(strongQueue);
    NSLog(@"%s", label);
});

2.最后,这个小金块埋在人 dispatch_data_create_map。GCD函数dispatch_data_create_mapdispatch_data_apply创建内部对象,并且在使用时必须格外小心。如果父GCD对象被释放,那么内部对象就会被吹走,坏事也会发生。父 dispatch_data_t上的__strong或objc_precise_lifetime可以帮助保持父对象存活。

// dispatch_data_create_map returns a new GCD data object.
// However, since we are not using it, the object is immediately
// destroyed by ARC and our buffer is now a dangling pounter!
dispatch_data_create_map(data, &danglingBuffer, &bufferLen);

// By stashing the results in a __strong var, our buffer
// is no longer dangerous.
__strong dispatch_data_t newData = dispatch_data_create_map(data, &okBuffer, &bufferLen);

实践中的队列

Queues,像最强大的工具一样,但是不当使用会对程序照成伤害。实际使用中需要有些规则,以下是一些一般准则:
·队列的使用应该限制在需要主线程的任务上,并且必须是短的,以防止锁定UI。
·每个创建的串行队列应该有一个目的。
·每个创建的串行队列应该被命名/标记为适合该目的。
·并发队列上执行的任务必须是线程安全的。
以上第二步值得进一步探索,因为队列是轻量级的,你可以创建很多很多队列。最好有许多专门的串行队列,而不是将许多断开的任务放入一个或两个“巨型”串行/并发队列中。
典型的“有目的”队列看起来像这样:

//used for importing into Core Data so we don't block the UI
dispatch_queue_create("com.yourcompany.CoreDataBackgroundQueue", NULL);

//used to prevent concurrent access to Somefile
dispatch_queue_create("com.yourcompany.SomeFile.ReadWriteQueue", NULL);

//used to perform long calculations in the the background
dispatch_queue_create("com.yourcompany.Component.BigLongCalculationQueue", NULL);

实际队列使用通常涉及嵌套调度:

dispatch_queue_t background_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);
dispatch_async(background_queue, ^{
    // do some stuff that takes a long time here...

    // follow up with some stuff on the main queue
    dispatch_async(dispatch_get_main_queue(), ^{
        // Typically updating the UI on the main thread.
    });
});

这里我们在后台队列上启动一个长时间运行的任务。任务完成后, 我们将触发在主队列上执行的 UI 更新。
还要注意过度嵌套的调度。它妨碍了可读性和可维护性, 应该被认为是有点刺激性的代码气味。

高级研究

通常的知识来源适用于网络上的文件和通过Xcode以及WWDC关于GCD和块的文档。

传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351