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_after
和 dispatch_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_release
和dispatch_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_map
和dispatch_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和块的文档。