GCD之队列的实现和使用
一、什么是GCD?
以下是摘自苹果的官方说明。
Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。
二、GCD实现之Dispatch Queue
- 用于管理追加的Block的C语言层实现的FIFO队列
- Atomic函数中实现的用于排他控制的轻量级信号
- 用于管理线程的C语言层实现的一些容器
GCD的API全部包含在libdispatch库中的C语言函数。Dispatch Queue通过结构体和链表,被实现为FIFO队列。FIFO队列管理是通过dispatch_async等函数所追加的Block。
Block并不是直接加入FIFO队列,而是先加入Dispatch Continuation这一dispatch_continution_t类结构体中,然后再加入FIFO队列。该Dispatch Continuation用于记忆Block所属的Dispatch Group和其他一些信息,相当于一般常说的执行上下文。
Dispatch Queue可通过dispatch_set_target_queue函数设定,可以设定执行该Dispatch Queue处理的Dispatch Queue为目标。该目标可像串珠子一样,设定多个连接在一起的Dispatch Queue。但是在连接串的最后必须设定为Main Dispatch Queue,或各种优先级的Global Dispatch Queue,或是准备用于Serial Dispatch Queue的各种优先级的 Global Dispatch Queue.
Global Dispatch Queue有如下8种。
- Global Dispatch Queue (High Priority)
- Global Dispatch Queue (Default Priority)
- Global Dispatch Queue (Low Priority)
- Global Dispatch Queue (Background Priority)
- Global Dispatch Queue (High Overcommit Priority)
- Global Dispatch Queue (Default Overcommit Priority)
- Global Dispatch Queue (Low Overcommit Priority)
- Global Dispatch Queue (Background Overcommit Priority)
优先级中附有Overcommit的Global Dispatch Queue使用在Serial Dispatch Queue中。如Overcommit这个名称所示,不管系统状态如何,都会强制生成线程的Dispatch Queue。
这8种Global Dispatch Queue各使用1个pthread_workqueue。GCD初始化时,使用pthread_workqueue_create_np函数生成pthread_workqueue。
pthread_workqueue包含在Libc提供的pthreads API中。其使用bsdthread_register和workq_open系统调用,在初始化XNU内核的workqueue之后获取workqueue信息。
XNU内核持有4中workqueue。
- WORKQUEUE_HIGH_PRIOQUEUE
- WORKQUEUE_DEFAULT_PRIOQUEUE
- WORKQUEUE_LOW_PRIOQUEUE
- WORKQUEUE_BG_PRIOQUEUE
以上为4种执行优先级的workqueue。该执行优先级与Global Dispatch Queue的4种执行优先级相同。
Dispatch Queue中执行Block的过程。
当在Global Dispatch Queue中执行Block时,libdispatch从Global Dispatch Queue自身的FIFO队列中取出Dispatch Continuation,调用pthread_workqueue_additem_np函数。将该Global Dispatch Queue自身、符合其优先级的workqueue信息以及为执行Dispatch Continuation的回调函数等传递给参数。
pthread_workqueue_additem_np函数使用workq_kernreturn系统调用,通知workqueue增加应当执行的项目。根据该通知,XNU内核基于系统状态判断是否需要生成线程。如果是Overcommit优先级的Global Dispatch Queue,workqueue则始终生成线程。
workqueue的线程执行pthread_workqueue函数,该函数调用libdispatch的回调函数。在该回调函数中执行加入到Dispatch Continuation的Block。
Block执行结束后,进行通知Dispatch Group结束、释放Dispatch Continuation等处理,开始准备执行加入到Global Dispatch Queue中的下一个Block。
以上就是Dispatch Queue执行的大概过程。
三、线程和队列
线程是代码执行的路径,队列则是用于保存以及管理任务的,线程负责去队列中取任务进行执行。
1、队列
是管理线程的,相当于线程池,能管理线程什么时候执行。
队列分为串行队列和并行队列等
串行队列:队列中的任务按顺序执行
并行队列:队列中的任务会并发执行。任务执行完毕了,不一定出队列。只有前面的任务执行完了,才会出队列。
串行队列:队列中的任务只会顺序执行,多个串行队列可并行执行
dispatch_queue_t q = dispatch_queue_create(“xxx”,DISPATCH_QUEUE_SERIAL);
并行队列:队列中的任务会并发执行
dispatch_queue_t q = dispatch_queue_create(“xxx”, DISPATCH_QUEUE_CONCURRENT);
全局队列:与并行队列类似,但调试时,无法确认操作所在队列
dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0);
主队列:每一个程序对应唯一一个主队列;在多线程开发中,使用主队列更新UI
dispatch_queue_t q = dispatch_get_main_queue();
2、同步和异步
dispatch_async (异步操作函数),就是将指定的Block“非同步”地追加到指定的队列(queue)中。dispatch_async函数不做任何等待。会新开线程
dispatch_sync( 同步操作函数),就是将指定的Block“同步”地追加到指定的队列(queue)中。在追加Block结束之前,dispatch_sync函数会一直等待;不会新开线程
3、队列和操作的组合
串行队列同步操作:同步操作不会新开线程、操作顺序执行
串行队列异步操作:异步操作新开一个子线程、操作顺序执行,“最安全的选择”
并行队列同步操作:同步操作不会新开线程、操作顺序执行
并行队列异步操作:异步操作会新开多个线程(有多少任务,就开n个线程执行)、操作无序执行;队列前如果有其他任务,会等待前面的任务完成之后再执行;场景:既不影响主线程,又不需要顺序执行的操作!
全局队列异步操作:异步操作会新建多个线程、操作无序执行,队列前如果有其他任务,会等待前面的任务完成之后再执行
全局队列同步操作:同步操作不会新建线程、操作顺序执行
主队列异步操作:异步操作都在主线程上顺序执行的,不存在异步的概念
主队列同步操作:会死锁
4、会引起死锁的2种情况
1、在主线程中运用主队列同步。
– (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@”hello”);
});
}
同步对于任务是立刻执行的,那么当把任务放进主队列时,它就会立马执行,只有执行完这个任务,viewDidLoad才会继续向下执行。
而viewDidLoad和任务都是在主队列上的,由于队列的先进先出原则,任务又需等待viewDidLoad执行完毕后才能继续执行,viewDidLoad和这个任务就形成了相互循环等待,就造成了死锁。
想避免这种死锁,可以将同步改成异步dispatch_async,或者将dispatch_get_main_queue换成其他串行或并行队列,都可以解决。
2、在串行队列中同步的向这个串行队列追加Block
dispatch_queue_t serialQueue = dispatch_queue_create(“xxx”, DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue, ^{
NSLog(@”hello”);
});
});
想避免这种死锁,可以将同步改成异步dispatch_async,或者将串行队列换为并行队列,都可以解决。
以上部分内容参考自《Objective-C高级编程》一书