写在前面
llibdispatch是苹果开发的一套C接口的库(苹果官方称之为GCD,Grand Central Dispatch),多线程技术的封装是其核心之一。GCD内部管理线程池,对上层以队列的形式提供操作接口,任务则以block的形式提交到队列,最终由GCD决策如何将任务派发到线程执行。
之前有翻译过raywenderlich中关于GCD的两篇文章,里面比较清楚的讲解了串行、并发的概念以及基本的API说明。今天来重新整理一遍libdispatch相关内容,其缘由是前不久项目中发现对队列与线程的一些误解。记录与分享之。
背景知识
并行与并发
parallelism与concurrency的区别,这个在raywenderlich也有说明。简而言之,并行(parallelism)在计算机领域则指的是机器指令的同时执行,也就是多个处理器执行;并发(concurrency)指的是逻辑层面的同时进行,其对立面是sequentially、任务间有先后顺序的处理方式。不太恰当的举个例子,项目开发上我们不同团队之间会是并行开发,比如后端、前端、美术团队是同时进行;而至于你,则一边听着音乐、一边码着代码。前者强调的是物理线路的分离,后者强调的是逻辑依赖的分离。并行的必要条件是并发,但并发不是并行的充分条件。放到所举栗子中,项目中不同团队的并行开发要求不同团队中的任务是没有依赖的,而就算任务不存在依赖,如果没有这样的团队划分(比如人员紧缺、职责混乱)也无法达成并行开发。
同步与异步
synchronous与asynchronous,同步/异步在计算机领域多线程的语境下,通常用来用来描述两个(及以上)操作之间的协作关系。程序执行中的状态通常称之为上下文(Context),切换进程、线程以及函数调用都需要切换上下文。我们知道,每个线程是一个无分叉的CPU命令执行序列。我们发起一个过程调用时,函数调用栈会将上一个函数的上下文信息(栈幁,活动记录,可参考wiki)push到栈中,直到函数处理结束后栈幁pop出,指令执行指针(如PC或IP)才会被重置到发起调用前代码的下一条指令。同步调用或异步调用(回调),描述的是消息的通知模式,也就是被调用的函数过程的执行结果,是通过返回值还是通过回调函数/闭包的形式来通知调用者,前者是同步调用后者是异步调用。这是两个函数过程的不同的协作模式。所以,像“异步函数”这种是很不恰当而奇怪的称呼。
所以所谓“同步队列”、“异步队列”这些是误用。
多线程方案
按抽象层次从低到高列举简单介绍如下:
- pthread 符合POSIX标准的libc(或glibc)中的线程库实现
- NSThread 苹果的基于pthread实现的OC接口的线程管理类
- GCD 苹果实现的C接口的任务/队列管理,线程池管理
- NSOperation 基于GCD的OC接口的任务管理,添加了任务依赖等特性
目前最常用莫过于GCD与NSOperation了。
GCD基础知识
串行、并发队列
GCD中所有队列都是FIFO的方式对任务的出列进行管理,可以分为两大类:串行队列、并发队列。
其中串行执行队列,必须等每个出列的任务执行完后才将下一个任务出列;而并发执行队列则是当前任务出列后,无需等待执行完毕,在并发数允许的情况下,可以将下一个任务出列。
需要先提一下,GCD中的任务dispatch通常会有两组接口,一组是dispatch[_xxx]_sync(queue, task)
,一组是
dispatch[_xxx]_async(queue, task),其功能都是将任务
task提交到队列
queue中执行。这里的
sync与
async的概念可能与"过程阻塞调用"更接近。前者是派发同步调用的任务,后者派发异步调用的任务。
dispatch[_xxx]_sync(queue, task)的函数调用,会想办法实现,在
task执行完之后才继续执行调用前的下一条指令;而
dispatch[_xxx]_async(queue, task)则仅是将
task添加到
queue中,然后该函数执行完毕继续下一条指令,至于
task`什么时候执行它不关心。当并发数为1时,并发队列等同于串行队列。
GCD的队列细分可按下表所示划分为4种(见下表示)。主线程队列是个特殊的串行队列,可通过dispatch_get_main_queue()
来取得。对于并发队列,提供提供了几个不同优先级的全局的并发队列。用户可自定义队列,接口为dispatch_create_queue(label, attributes)
。
队列 |
类型 |
创建/获取 |
备注 |
主队列 |
串行 |
dispatch_get_main_queue() |
|
全局并发队列 |
并发 |
dispatch_get_global_queue(priority, flag) |
|
自定义串行队列 |
串行 |
dispatch_create_queue(“com.queue.name”, NULL) |
需锚定 |
自定义并发队列 |
并发 |
dispatch_create_queue(“com.queue.name”, DISPATCH_QUEUE_CONCURRENT) |
需锚定 |
示例:
1 2 3 4
| // 创建串行队列 dispatch_create_queue("com.queue.serial", NULL) // 创建并发队列 dispatch_create_queue("com.queue.concurrent", DISPATCH_QUEUE_CONCURRENT)
|
线程池
GCD对上层提供的概念是队列的概念,并自身维护了线程池,libdispatch会按需将队列中的任务派发到具体的线程中执行。
但队列与线程并非一一对应的关系。这是个很容易被误解的地方。(当然我这次也踩了这个坑)
提交到同一个队列的任务,并不一定在同一个线程中执行。即使是串行队列。
举个实际的例子:
为了解决数据访问的不一致问题(实际是Realm数据库读写),我们创建了一个串行队列syncqueue
,会有dispatch_sync()
以及dispatch_async()
两种调用。我们的预期是,这不是串行队列吗?上面两种调用,是会在同一个线程中执行的。你觉得呢?sync
与async
的调用并不能保证是同一个线程中的操作,而Realm本身为速度而生、是一种mvcc结构,是允许多线程的同时读写,它的数据一致性是通过数据更新来实现的。结果可想而知,调用了delete
异步写入之后,并不能反映到随后的同步query
结果中。
事实上,是我们混淆了队列与线程的概念、关系。在上面例子中,sync
与async
的操作依然是遵循队列FIFO串行出列的特性的,但sync
操作往往被dispatch到调用线程中执行,而async
操作往往被dispatch到一个新的线程中执行。(可通过下面的测试进行验证)
所以务必记住线程池的概念,区分队列与线程。
主线程队列
主线程队列有什么特殊?简要的说,这是个跟应用主线程关联的队列。
首先,只有主线程队列结合到主runloop
中去了,主线程队列提交的任务一定在主线程执行。因为是串行队列,所有任务按提交的时间先后执行,在每次runloop循环中检查是否有任务并执行。非线程安全的UI操作,默认都需要在主线程(队列)中执行。
再者,在主队列异步提交的任务,也只会在主线程执行,而不会开辟新的线程执行。这是个容易被忽略的常识。因为对于异步提交到非主线程队列的任务,通常会被提交到另一个具体线程(与当前线程不同)执行。
目标队列
主线程队列任务会在主线程中执行,全局并发队列中任务会被派发到具体线程中执行。但自定义队列中的任务不会直接被提交到具体线程中,而是需要锚定以上两种队列,称之为目标队列。自定义队列中的任务会被提交到目标队列中。
可使用dispatch_set_target_queue(object, targetqueue)
来指定目标队列。
一些坑点
dispatch[_xxx]_sync
的不当使用导致死锁
在串行队列中,派发同步调用的任务,必定导致死锁。例如在主线程中(或dispatch_async()
到mainQueue的任务block中),调用dispatch[_xxx]_sync
必定死锁;在其他串行队列同样如此(但并发队列不会,虽然官方不建议)。
dispatch_after(delta, queue, task)
,在指定时间追加任务到队列中。
测试
以下是一些基本测试,通过这些测试代码,你可能更好的体会串行/并发、同步/异步的概念。以下代码均在主线程调起。其中,syncq、syncq2为2个新建的串行执行队列,asyncq为新建的并发执行队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| NSLog(@"[Thread][0]: %@",[NSThread currentThread]); // 全部输出为:[Thread][0]: <NSThread: 0x60000007b440>{number = 1, name = main},后文简称main dispatch_sync(syncq, ^{ NSLog(@"[Thread][1]: %@",[NSThread currentThread]);
dispatch_sync(asyncq, ^{ NSLog(@"[Thread][2]: %@",[NSThread currentThread]);
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][3]: %@",[NSThread currentThread]); }); });
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][4]: %@",[NSThread currentThread]); }); });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| NSLog(@"[Thread][0]: %@",[NSThread currentThread]);//main dispatch_async(syncq, ^{ // 以下输出全为:[Thread][1]: <NSThread: 0x6000002653c0>{number = 3, name = (null)} NSLog(@"[Thread][1]: %@",[NSThread currentThread]);
dispatch_sync(asyncq, ^{ NSLog(@"[Thread][2]: %@",[NSThread currentThread]);
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][3]: %@",[NSThread currentThread]); }); });
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][4]: %@",[NSThread currentThread]); }); });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| NSLog(@"[Thread][0]: %@",[NSThread currentThread]); // [Thread][0]: <NSThread: 0x60000007b440>{number = 1, name = main} dispatch_sync(syncq, ^{ NSLog(@"[Thread][1]: %@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{// 死锁 NSLog(@"[Thread][2]: %@",[NSThread currentThread]);
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][3]: %@",[NSThread currentThread]); }); });
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][4]: %@",[NSThread currentThread]); }); });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| NSLog(@"[Thread][0]: %@",[NSThread currentThread]);//main dispatch_sync(syncq, ^{ NSLog(@"[Thread][1]: %@",[NSThread currentThread]);
dispatch_async(asyncq, ^{ NSLog(@"[Thread][2]: %@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"[Thread][3]: %@",[NSThread currentThread]); }); });
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][4]: %@",[NSThread currentThread]); }); });
输出为: [Thread][0]: <NSThread: 0x6000000701c0>{number = 1, name = main} [Thread][1]: <NSThread: 0x6000000701c0>{number = 1, name = main} [Thread][4]: <NSThread: 0x6000000701c0>{number = 1, name = main} [Thread][2]: <NSThread: 0x60000007d040>{number = 3, name = (null)} [Thread][3]: <NSThread: 0x6000000701c0>{number = 1, name = main}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| NSLog(@"[Thread][0]: %@",[NSThread currentThread]);//main dispatch_sync(syncq, ^{ NSLog(@"[Thread][1]: %@",[NSThread currentThread]);
dispatch_async(asyncq, ^{ NSLog(@"[Thread][2]: %@",[NSThread currentThread]);
dispatch_sync(asyncq, ^{ NSLog(@"[Thread][3]: %@",[NSThread currentThread]); }); });
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][4]: %@",[NSThread currentThread]); }); });
输出: [Thread][0]: <NSThread: 0x60800007ea80>{number = 1, name = main} [Thread][1]: <NSThread: 0x60800007ea80>{number = 1, name = main} [Thread][4]: <NSThread: 0x60800007ea80>{number = 1, name = main} [Thread][2]: <NSThread: 0x60000026c840>{number = 3, name = (null)} [Thread][3]: <NSThread: 0x60000026c840>{number = 3, name = (null)}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| NSLog(@"[Thread][0]: %@",[NSThread currentThread]);//main dispatch_sync(asyncq, ^{ NSLog(@"[Thread][1]: %@",[NSThread currentThread]);
dispatch_async(syncq, ^{ NSLog(@"[Thread][2]: %@",[NSThread currentThread]);
dispatch_sync(syncq, ^{// 死锁 NSLog(@"[Thread][3]: %@",[NSThread currentThread]); }); });
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][4]: %@",[NSThread currentThread]); }); });
输出: [Thread][0]: <NSThread: 0x600000077980>{number = 1, name = main} [Thread][1]: <NSThread: 0x600000077980>{number = 1, name = main} [Thread][4]: <NSThread: 0x600000077980>{number = 1, name = main} [Thread][2]: <NSThread: 0x608000262d80>{number = 3, name = (null)}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| NSLog(@"[Thread][0]: %@",[NSThread currentThread]);//main dispatch_sync(asyncq, ^{ NSLog(@"[Thread][1]: %@",[NSThread currentThread]);
dispatch_async(syncq, ^{ NSLog(@"[Thread][2]: %@",[NSThread currentThread]);
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][3]: %@",[NSThread currentThread]); }); });
dispatch_sync(syncq, ^{//syncq等待? NSLog(@"[Thread][4]: %@",[NSThread currentThread]); }); });
输出: [Thread][0]: <NSThread: 0x60800007b740>{number = 1, name = main} [Thread][1]: <NSThread: 0x60800007b740>{number = 1, name = main} [Thread][2]: <NSThread: 0x600000266700>{number = 3, name = (null)} [Thread][3]: <NSThread: 0x600000266700>{number = 3, name = (null)} [Thread][4]: <NSThread: 0x60800007b740>{number = 1, name = main}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| NSLog(@"[Thread][0]: %@",[NSThread currentThread]);//main dispatch_sync(asyncq, ^{ NSLog(@"[Thread][1]: %@",[NSThread currentThread]);
dispatch_async(syncq, ^{ NSLog(@"[Thread][2]: %@",[NSThread currentThread]);
dispatch_sync(syncq2, ^{ NSLog(@"[Thread][3]: %@",[NSThread currentThread]); }); });
dispatch_sync(asyncq, ^{ NSLog(@"[Thread][4]: %@",[NSThread currentThread]); }); });
输出: [Thread][0]: <NSThread: 0x600000067e80>{number = 1, name = main} [Thread][1]: <NSThread: 0x600000067e80>{number = 1, name = main} [Thread][4]: <NSThread: 0x600000067e80>{number = 1, name = main} [Thread][2]: <NSThread: 0x600000073580>{number = 3, name = (null)} [Thread][3]: <NSThread: 0x600000073580>{number = 3, name = (null)}
|
本文主要介绍GCD的一些基本背景知识以及基础知识,更详细的介绍以及拓展知识,将在后续系列中介绍。
本文完。
Comments