Objective-C高级编程-GCD部分
Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
GCD的简单使用
dispatch_async(queue, ^{
/*
* 想在后台处理的事务,如数据库访问,大量的计算等
*/
dispatch_async(dispatch_get_main_queue(), ^{
/*
* 在主线程执行的事务,如界面更新
*/
});
});
这里dispatch_async的作用就是讲开发者定义的想执行的任务追加到适当的Dispatch Queue中。
dispatch_async(queue, ^{
//想执行的任务
});
其中需要传入两个参数,第一个是Dispatch Queue,第二个是Block变量。
这里dispatch_async将任务异步的追加到Dispatch Queue中,Dispatch Queue会按照FIFO的顺序执行处理。
这里Dispatch Queue有两种
Dispatch Queue的种类 | 说明 |
---|---|
Serial Dispatch Queue | 等待现在执行中处理结束 |
Concurrent Dispatch Queue | 不等待现在执行中处理结束 |
简单来讲如果使用Serial Dispatch Queue来处理事务,事务的处理顺序会按照加入的顺序执行。
如果使用Concurrent Dispatch Queue来处理事务,则后面添加的事务不会等待前面的事务执行完成后才执行,也就是不保证事务的处理顺序是加入的顺序。加入的事务是并行处理,但并行处理的处理数量取决于当前系统的状态,即iOS和OS X基于Dispatch Queue中的处理数、CPU核数以及CPU负荷等当前系统的状态来决定Concurrent Dispatch Queue中并行执行的处理数。
获取Dispatch_Queue
1. dispatch_queue_create
函数
dispatch_queue_t queue = dispatch_queue_create("MySerialDispatchQueue", NULL);
第一个参数是名称,可以方便调试。
第二个参数可以指定获取的是Serial Dispatch Queue还是Concurrent Dispatch Queue,参数为DISPATCH_QUEUE_CONCURRENT
或者DISPATCH_QUEUE_SERIAL
。
通过dispatch_queue_create创建的Dispatch Queue在使用结束后必须通过dispatch_release
函数释放。这里对应的还有dispatch_retain
函数,即Dispatch Queue也像Objective-C的引用计数式内存管理一样,需要通过dispatch_retain
函数和dispatch_release
函数的引用计数来管理内存。
使用时可以在使用完
dispatch_async
函数之后立即使用dispatch_release
函数进行释放,因为实际上传入dispatch_async
函数的Block语法强持有了queue
对象,所以不用担心在调用了dispatch_release
函数后导致queue
被废弃。
2. Main Dispatch Queue/Global Dispatch Queue
Main Dispatch Queue获取主线程执行的Dispatch Queue。
Global Dispatch Queue获取所有应用程序都能够使用的Concurrent Dispatch Queue,它有4个优先级,分别是高、默认、低和后台,执行优先级只是大致的判断,并不能保证实时性。
名称 | Dispatch Queue的种类 | 说明 |
---|---|---|
Main Dispatch Queue | Serial Dispatch Queue | 主线程执行 |
Global Dispatch Queue(High Priority) | Concurrent Dispatch Queue | 执行优先级:高 |
Global Dispatch Queue(Default Priority) | Concurrent Dispatch Queue | 执行优先级:默认 |
Global Dispatch Queue(Low Priority) | Concurrent Dispatch Queue | 执行优先级:低 |
Global Dispatch Queue(Background Priority) | Concurrent Dispatch Queue | 执行优先级:后台 |
各种Dispatch Queue获取方法如下:
//Main Dispatch Queue
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
//Global Dispatch Queue High Priority
dispatch_queue_t globalDispatchQueueHight =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
这里其他几个优先级对应的参数为DISPATCH_QUEUE_PRIORITY_DEFAULT
、DISPATCH_QUEUE_PRIORITY_LOW
、DISPATCH_QUEUE_PRIORITY_BACKGROUND
。
关于GCD中注意的问题
数据竞争
如多个线程要修改同一个变量或者文件,最好将这些操作放在同一个Serial Dispatch Queue来执行,但比如读取操作就可以并行执行。
可能会用到的API
1. Serial Dispatch Queue
使用该queue可以有效避免数据竞争问题。
2. Dispatch Group
该API会将追加到Dispatch Group中的多个处理全部执行完成后执行结束处理。
3. dispatch_barrier_async
函数
在访问数据库或文件时,想在多个读取操作后,进行一次写入操作,然后再执行多个读取操作,可以使用该函数,简化代码的逻辑。
//获取一个Concurrent Dispatch Queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//并行执行多个读操作
dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_async(queue, blk2_for_reading);
dispatch_async(queue, blk3_for_reading);
//这一步会等待上面的所有并行任务执行完毕才会执行
dispatch_barrier_async(queue, blk_for_writing);
//写操作执行完毕后,继续并行执行剩下的读操作
dispatch_async(queue, blk4_for_reading);
dispatch_async(queue, blk5_for_reading);
dispatch_async(queue, blk6_for_reading);
dispatch_async(queue, blk7_for_reading);
死锁
使用dispatch_sync可能会导致死锁,如下代码是在主线程中运行的话就会导致死锁:
//在主线程中获取主线程的Dispatch Queue
dispatch_queue_t queue = disaptch_get_main_queue();
//向main dispatch queue中同步添加一个Block
dispatch_sync(queue, ^{
NSLog(@"Heollo?");
});
这段代码导致死锁的原因是,dispatch_sync是同步追加的函数,意味着进入该函数后,需要将Block代码添加到Queue中并且执行完成后才会返回,而该函数没有返回之前,主线程就会等待在这一步,并不会执行加入到Main Dispatch Queue中的Block语句,最终导致死锁的产生。
不要使用太多线程
太多的线程会导致消耗大量的内存,在使用过程中,需要结合使用环境,当前需要处理的逻辑等来决定多线程的使用,不要滥用。
GCD的实现
这部分书中着墨不多,主要包括两个部分,一个是常用的Dispatch Queue,另一个是不太引人瞩目的Dispatch Source。
Dispatch Queue
我们可以想象,GCD的实现最基本的需要以下这些工具
- 用于管理追加的Block的C语言层实现的FIFO队列
- Atomic函数中实现的用于排他控制的轻量级信号
- 用于管理线程的C语言层实现的一些容器
而苹果的官方说明中提到
通常,应用程序中编写的线程管理用的代码要在系统级实现。
实质上,GCD的实现是在iOS和OS X的核心XNU内核级上实现的。编程人员无论如何努力编写管理线程的代码,在性能方面也不可能胜过XNU内核级所实现的GCD。
以下软件组件用于实现Dispatch Queue
组件名称 | 提供技术 |
---|---|
libdispatch | Dispatch Queue |
Libc(pthreads) | pthread_workqueue |
XNU内核 | workqueue |
其中,我们使用GCD时使用到的API全部为包含在libdispatch库中的C语言函数。
libdispatch部分
Dispatch Queue通过结构体和链表,被实现为FIFO队列,这个队列管理的是使用dispatch_async
等函数时传入的Block。该Block首先被包装成disptch_continuation_t
类型的结构体中,该结构体存储Block以及Block所属的Dispatch Group和一些其他的信息(通常说的执行上下文)。
Dispatch Queue可通过dispatch_set_target_queue
函数来设定,可以将多个Dispatch Queue连接起来,让本来并行执行的任务变为串行执行。但连接串的最后必须设定为Main Dispatch Queue,或各种优先级的Global Dispatch Queue,或是准备用于Seiral Dispatch Queue的各种优先级的Global Dispatch Queue。
其中有8种Global Dispatch Queue:
- 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,表示不管系统状态如何,都会强制生成线程的Dispatch Queue。
pthreads部分
上面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部分
XNU内核持有4中workqueue。
- WORKQUEUE_HIGH_PRIOQUEUE
- WORKQUEUE_DEFAULT_PRIOQUEUE
- WORKQUEUE_LOW_PRIOQUEUE
- WORKQUEUE_BG_PRIOQUEUE
这4个优先级与Global Dispatch Queue的4中执行优先级相同。
Dispatch Queue执行Bloack的过程
- libdispatch从Global Dispatch Queue自身的FIFO队列中取出Dispatch Continuation,调用
pthread_workqueue_additem_np
函数。将该Global Dispatch Queue自身、符合其优先级的workqueue信息以及为执行Dispatch Continuation的回调函数等传递给参数。
XNU内核基于系统状态判断是否要生成线程。如果是Overcommit优先级的则始终生成线程。
workqueue生成的线程在实现用于workqueue的线程计划表中运行,所以与一般线程的上下文切换不同。这里也隐藏着使用GCD的原因。该线程虽然与iOS和OS X中通常使用的线程大致相同,但是有有一部分pthread API不能使用。可参考官方文档《并列编程指南》的”与POSIX线程的互换性“一节。
(翻译真的很烂,猜测是这样的线程生成效率高)
回调
workqueue的线程执行pthread_workqueue
函数,该函数调用libdispatch的回调函数,在该回调函数中执行加入到Dispatch Continuation的Block。
Block执行结束后,进行通知Dispatch Group结束、释放Dispatch Continuation等处理,开始准备执行加入到Global Dispath Queue中的下一个Block。
小结
XNU内核系统级的实现是GCD高性能的最大的原因,由编程人员管理的线程中,想发挥出匹敌GCD的性能是不可能的。
总结
多线程编程是一种易发生各种问题的编程技术,虽然回避这些问题有许多方法,且程序都偏于复杂,但使用多线程编程的好处显而易见,保证应用程序的响应性能,而GCD大大简化了偏于复杂的多线程编程的源代码。
参考
《Objective-C高级编程 iOS与OS X多线程和内存管理》
并列编程指南