笔者本科刚毕业,做iOS也有半年了,一直觉得纯工作不总结没什么沉淀。看到简书那么多大神在做学习笔记,于是决定也跟着做点,希望对自己有帮助。如果能顺道帮到一两个同道,那将更是完美了。
今天的话题是GCD。笔者由于缺乏实战经验,总觉得多线程什么的处理起来非常复杂,于是决定花点时间把GCD搞懂。参考上主要看了dullglass大神的总结,获益匪浅,特此鸣谢。下面是这篇文章的总纲:
- GCD存在的意义是什么
- GCD队列
- GCD分发模式
- 实战
为什么需要知道GCD
GCD就是为了简化对多线程的管理而存在的。有了GCD的存在,我们就不再需要处理繁琐又容易出错的线程管理。GCD本身管理着一个线程池,而把线程的概念抽象成了队列(所以线程 != 队列)。系统会根据现在的状态分配线程池里的线程去执行一个或多个队列里的任务(注意一个线程可能同时在执行多个队列里的任务的情况)。有了GCD以后,程序员要做的就只是告诉GCD你要做什么,分配到哪个队列上去执行,剩下的事情就可以交给GCD了。那么问题来了,什么是队列?GCD是怎么通过队列的概念把这些繁琐的细节抽象出来的?
废话不说了,直接上图。
GCD 队列
GCD队列和数据结构的队列概念上是一样的,用以管理待执行的任务。如上图所示,iOS系统默认已经有了5个队列,系统相关任务就是在这五个队列上执行的。所以要注意,写代码时如果用到这五个队列,一定不能假定队列上只有你自己添加的任务(因为系统也会往里面添加)。
GCD的队列按其性质可分为三种:
- 线性队列 (Serial Dispatch Queue)
- 并发队列 (Concurrent Dispatch Queue)
- 主队列 (Main Dispatch Queue)
线性队列
线性队列上的任务按照FIFO的原则被依次执行。在彻底执行完队列最前面的任务前,队列的其他任务可以保证不被开始执行。这也是线性队列存在的意义所在:它能保证任务执行的顺序,从而在某种程度上解决了线程安全的问题(虽然线性的方式效率比较低)。比如说,当你需要控制对某个资源的读写,保证任何时刻最多只能有一个线程在对其进行更改时,就可以采用线性队列,把所有读写的操作都放到同一个线性队列上执行。线性队列的实际应用大家可以参考笔者写的关于CocoaLumberjack的一篇文章。
关于线性队列还有很重要的一点,就是虽然队列里的任务执行的顺序是依次的,但是执行每个任务的线程却不一定是同一个!!(这点知道一下就好了,其实对于具体的代码实现也不会有什么影响,毕竟GCD存在的意义就是让我们不用考虑线程的问题。)
并发队列
和线性队列的区别在于,并发队列可以把手头的任务分发给不同的线程同时执行。也就是说,并发队列并不会等待上一个任务完成了之后再分发下一个任务。即便如此,它还是保证了任务开始执行的时间是FIFO的。并发队列的主要用途就是处理一些计算量大的任务,比如数据的传输等等,使其不至于阻塞当前队列。系统默认的全局队列就是并发队列。
主队列
主队列由系统创建,它的本质也是一个线性队列。主队列上的所有任务,一定会在主线程执行(反之则不然,主线程有可能执行其他队列上的任务)。由于主队列主要负责系统Runloop的操作,阻塞主队列(往主队列上分发过多或者过于复杂的任务)将会造成APP对用户操作的反应过慢,从而极大影响用户对APP的体验。
好了,虽然话有点多,但至少我们把队列的事情搞清楚了。接下来就是怎么分发任务到队列了。
GCD分发模式
众所周知,GCD分发模式有两种:同步分发(dispatch_sync
)和异步分发(dispatch_async
)。这两个的区别和用法其实非常简单,一句话就能概括:当前代码要不要等到分发的任务执行完了再继续执行。如果要等,就用同步分发,否则就用异步分发。
说实话,笔者第一次看到这些的时候,最大的疑虑就是为什么会需要dispatch_sync
,总觉得那不跟没写这个dispatch没什么区别么,反正都是按顺序执行的。。。(毕竟当时还是一脸懵逼,这些概念完全都没搞懂。。。)
实战
最后我们来举几个栗子,毕竟说了这么多没几个栗子太不像话了。下面的例子笔者不做分析,也建议读者先不要看答案,自行分析,如果笔者所说的都理解透彻了,精确分析出答案应该不费吹灰之力。
以下例子假设:
dispatch_queue_t serialQueue =
dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue =
dispatch_queue_create("serial", DISPATCH_QUEUE_CONCURRENT);
示例1
NSLog(@"Start");
dispatch_sync(serialQueue, ^{
[NSThread sleepForTimeInterval:3.0f];
NSLog(@"Task 1");
});
dispatch_sync(serialQueue, ^{
[NSThread sleepForTimeInterval:1.0f];
NSLog(@"Task 2");
});
NSLog(@"End");
结果将是:
Start
Task 1
Task 2
End
示例2
NSLog(@"Start");
dispatch_async(serialQueue, ^{
[NSThread sleepForTimeInterval:3.0f];
NSLog(@"Task 1");
});
dispatch_async(serialQueue, ^{
[NSThread sleepForTimeInterval:1.0f];
NSLog(@"Task 2");
});
NSLog(@"End");
结果将是:
Start
End
Task 1
Task 2
示例3
NSLog(@"Start");
dispatch_sync(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3.0f];
NSLog(@"Task 1");
});
dispatch_sync(concurrentQueue, ^{
[NSThread sleepForTimeInterval:1.0f];
NSLog(@"Task 2");
});
NSLog(@"End");
结果将是:
Start
Task 1
Task 2
End
示例4
NSLog(@"Start");
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3.0f];
NSLog(@"Task 1");
});
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:1.0f];
NSLog(@"Task 2");
});
NSLog(@"End");
结果将是:
Start
End
Task 2
Task 1
写在最后
GCD其实本身并不是很复杂,理解不是很难,但是要完全掌握得心应手,还得靠实战。笔者在这方面还是菜鸟一个,只能说在理解上希望能帮到大家。文章有什么需要改正的欢迎大家指出改正,有什么问题也欢迎提出一起讨论。
(BTW,这还是我在简书上的处女作,哈哈。。。希望能帮到有需要的人。)
网友评论
(1)并发队列:不会开线程
(2)串行队列:不会开线程
异步函数
(1)并发队列:能开启N条线程
(2)串行队列:开启1条线程