GCD是苹果为多核并行运算提出的方案,可以更高效的利用CPU。但是更重要的是它使多任务处理更加高效。因为它会自动合理的运用多核CPU。并且自从GCD的内存管理也加入ARC之后,它就能自动管理线程的生命周期。我们只需要把需要的操作(block)告诉它就可以了。
凡说GCD,肯定要说两个概念
任务和队列,任务可以同步执行和异步执行,存放任务的队列分为串行队列和并行队列。
一.任务 (block)
就是咱们要执行的操作,GCD中就是block,任务执行在哪个线程由执行方式来决定
1.同步执行(sync)
会阻塞当前任务所在的线程(同步线程),dispatch_sync(queue, ^(block))把block放到queue中在当前线程执行。
2.异步执行(async)
不会阻塞当前任务所在的线程(异步线程),dispatch_async(queue, ^(block)),如果是串行队列,则只开一个线程,如果是并行队列,则开多个线程。
所以说sync和async决定block在哪个线程中执行
二.队列 (queue)
存放任务的地方,负责调度任务(block)
1.串行队列(SERIAL)顾名思义就是按顺序执行队列中的任务,一个任务完成再执行下一个任务。
主队列:dispatch_get_main_queue 是一个特殊的串行队列,运行在主线程中,UI相关操作都要在该队列中执行。
自定义串行队列:dispatch_queue_create("标识", DISPATCH_QUEUE_SERIAL),最后一个参数可以为NULL,默认创建的是串行队列
2.并行队列(CONCURRENT)就是很多任务并发执行,其实GCD中的并行队列也是根据FIFO的原则取出任务,不同的是取出任务后GCD会新开一个线程来执行任务。
全局队列:dispatch_get_global_queue(优先级,0); 苹果公开的全局并行队列,一共有四个优先级
自定义并行队列:dispatch_queue_create("标识", DISPATCH_QUEUE_CONCURRENT)
三.队列和任务是怎么执行的
GCD的基本概念虽然不多,但是用起来还是需要理解的深刻一些,比如任务在不同的队列类别里用不同的方式执行会造成什么样的结果,接下来咱们就来一项一项说。
1.串行队列同步执行
2.串行队列异步执行
执行结果:
1-4为串行队列同步执行
5-6为串行队列异步执行
可以看出来:1-4在当前线程一个一个执行,5-6新开了一个线程一个一个执行(如果串行队列为主队列,则在主线程中执行)
3.并行队列同步执行 4.并行队列异步执行
执行结果:
1-4为并行队列同步执行
5-6为并行队列异步执行
可以看出来:1-4在当前线程一个一个执行,5-6新开了多个线程并发执行
四.实战场景
理解了任务、队列的运行规则,下面就来看看实际项目中在何种场景下运用GCD
1.运用dispatch_async来避免一些耗时的任务阻塞主线程(卡死界面).
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"耗时的操作读取数据库,网络操作等");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"操作完成_刷新UI在:%@",i,[NSThread currentThread]);
});
});
这里可能有人会问:回到主线程的操作,dispatch_sync和dispatch_async有什么区别?
大家可以想想这个问题,如果理解了前面说的队列和任务,你应该知道结果,文章后面还会提到。
2.运用dispatch_apply进行快速迭代.
如果是串行队列,dispatch_apply和for是一样的,
如果运用在并发队列,那么dispatch_apply就会并发的执行任务(block),大大的提高遍历速度。
> 两种方法各循环100000次,for耗时32.9秒,apply耗时19.4秒
> 效率提高了40%多。
有人说可以在for循环里面用dispatch_async开启子线程执行任务,的确可以,但是如果开启的子线程多了,很有可能线程爆炸造成死锁等情况。而GCD会管理并发,所以apply还能避免线程爆炸的问题,实在是居家旅行、杀人灭口,必备良药。
3.dispatch_group 调度组
开发中我们经常会遇到这样的需求:同时调用多个接口,所有接口返回后再刷新界面。如果不用调度组的话,应该怎么做?大部分人都会把这几个接口串行起来,等最后一个接口返回再执行刷新界面的方法,这样做的话如果其中一个接口返回失败,那么整个页面就无法刷新了。还有的同学每个接口回来都刷新一次界面,这样会造成页面闪烁,严重的话,并发调用同时回来结果操作同一数据源,还有可能造成崩溃。
这时无疑用GCD调度组是最好的解决方法。调度组会在组里所有的任务执行完毕后发送一个通知告诉我们,组内的任务全部执行完毕。
接收通知的方式有两种,
同步执行的dispatch_block_wait,会阻塞当前线程并等待之前的任务全部执行完或超时再执行wait中队列里的任务
异步执行的dispatch_group_notify,作用和wait一样,但是是异步执行的,所以不会阻塞当前线程
dispatch_group_enter和dispatch_group_leave可以手动管理group中的任务计数,enter为+1,leave为-1,当计数为0时,才会进入wait或notify中的任务
具体执行看代码:这里用after延迟提交任务(block)的做法来模拟调用接口时的情景。
以上代码中创建了一个调度组group,并且指定调度组中的任务在全局队列中运行。在执行结果中可以看到,全局队列中先执行了任务2和任务1(强制停止了1秒),因为wait会在当前线程等待任务1,2都完成之后才执行,所以wait执行完后再执行任务3,4,这里用了手动计数的方法控制任务计数,当3,4都执行完后,计数归0,最后计入notify的任务,当我们需要同时并发执行多个接口之后再执行某项操作时,调度组非常实用。
4.GCD中容易遇到的死锁问题
GCD的任务和队列都是在线程中运行的,所以频繁的操作线程如果不注意会很容易造成死锁问题,所谓死锁,就是指两个线程互相等待对方完成某项操作,导致线程卡死,当然归根结底是对任务和队列的运行方式理解的还不够彻底,下面列举一些容易造成死锁的现象,coding中要格外注意。
案例1:最简单的死锁现象
控制台:
1.
我们来分析一下堆栈信息,根据FIFO的原则,任务1 - 同步线程-任务3-任务2
因为同步线程阻塞了主线程,所以任务2等待任务3执行,任务3又等待任务2,造成死锁,程序卡死。
这时如果我们再稍微复杂一些呢
案例2:串行队列中同步执行一个并行队列中的任务
控制台:
1
2
3
这个比较好理解,在主线程中打印1,这时同步线程在全局队列里面执行2,不会像1一样把任务2直接加在主队列队尾,所以不存在2,3相互等待的情况,而是同步线程阻塞了主线程后等待任务2执行完毕后,顺序执行3.
案例3:异步线程执行后回到同步线程执行
是不是很眼熟,这就是4.实战场景中的第一个案例,我们当然不是要演示这个,继续看下图两张图
控制台:
1
8
2
3
4
5
6
7
控制台:
1
8
2
7
3
4
5
6
控制台:
分析:输出0后,就是异步线程,所以任务4不用等待,1,4执行顺序不一定。任务4完成后,接着是一个阻塞线程的同步任务5,但是加入到全局队列的异步线程不受影响,继续执行1后面的同步线程中的任务2,并且任务3需要等任务2完成后才能执行。但是这时主线程已经被一个同步线程的任务5,和6死锁,所以任务2也无法执行。
总结
GCD除了上面说的这些,只要理解了他的运行方式,可以灵活的组合出很多用法,当然这篇文章只是说了GCD的凤毛麟角,NSOperation和NSOperationQueue还没有提到,先理解了GCD,咱们下次说NSOperation和NsOperationQueue。