原文1:https://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1
原文2:https://www.raywenderlich.com/63338/grand-central-dispatch-in-depth-part-2
一、并发与并行
2D222770-24F6-462D-A044-AC3F01BC4AAD.png代码同时执行有两种实现方式:
-
并行:多核并行(如图第一行),多核设备将不同的线程放在多个核上同时运行。
-
并发:单核上下文切换(如图第二行),单核设备只能通过极其快速以至于人无法察觉的上下文切换来造成并行的效果,实际上某一时间点,只有一段代码在执行。
GCD可以实现代码的同时执行,但是通过并行还是并发由GCD决定。
二、队列 Queues
GCD提供两种队列,用于添加需要被处理的任务(由Block表示)
-
串行队列 Serial Queues
GCD保证队列中任务执行的时机受到严格控制: GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。
-
并发队列
GCD保证队列中任务:按照被添加的顺序开始执行,完成顺序无法保证。
GCD的一些队列类型:
-
主队列(main queue):串行队列,保证所有任务在主线程执行,主线程是唯一可用于更新UI的线程。
-
四个全局队列(Global Dispatch Queue):均为串发队列,Apple API也会使用,分background、low、default 以及 high四个优先级。
-
自己实现的队列
三、GCD的API
GCD提供一些方法用于将任务块分发给各个队列,不同方法与不同队列的组合可以达到不同的效果。
Dispatch_aysnc
- 主队列: 在并发队列完成任务后,派发更新UI的工作到主队列;在主队列异步派发到主队列可以保证被派发的任务在当前方法完成后执行。
- 并发队列:后台执行非UI工作,比如基于网络或CPU紧张的,以免阻塞当前线程
- 自定义串行队列:串行执行可能出现race condition情况的任务
- 是否可以取消:iOS8后可以:dispatch_block_cancel
Dispatch_after
延迟版的dispatch_aysnc,但其方法返回之后就不可以取消了。最好在主队列使用,为什么?
Dispatch_once
竞争冒险(race hazard)又名竞态条件、竞争条件(race condition),它旨在描述一个系统或者进程的输出依赖于不受控制的事件出现顺序或者出现时机。举例来说,如果计算机中的两个进程同时试图修改一个共享内存的内容,在没有并发控制的情况下,最后的结果依赖于两个进程的执行顺序与时机。而且如果发生了并发访问冲突,则最后的结果是不正确的。
在软件中,导致race condition出现的那部分代码块,就叫临界区。dispatch_once保证以线程安全的方式只执行一次代码块,临界区(dispactch_once的block块)已有线程执行时,其他线程的访问会被阻塞。使用例子:
OC单例
+ (instancetype)sharedManager
{
static PhotoManager *sharedPhotoManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPhotoManager = [[PhotoManager alloc] init];
sharedPhotoManager->_photosArray = [NSMutableArray array];
});
return sharedPhotoManager;
}
但这样只能保证临界区(5~6行)的线程安全。由于sharedManager单例拥有可变属性photosArray,对其它的操作还是可能导致race condition,比如读者和写者同时操纵photosArray。
Dispatch barriers
dispatch barrier可以确保提交的 Block 在那个特定时间上是指定队列上唯一被执行的条目。所有的先于dispatch barrier提交到队列的任务都能在这个 Block 执行前完成。
相当于barrier block执行的时候,是串行队列
370B5A7B-157F-448A-8EC2-2AA1309E76F2.pngdispatch_barrier_async:异步派发阻塞
- 主队列:完全无意义,主队列本来就是串行的
- 并发队列:全局队列:可能影响系统对队列的使用,要谨慎。自定义队列:保证线程安全的绝佳选择。
- 自定义串行队列:无意义
dispatch_sync
dispatch_sync:同步派发
- 主队列:也有死锁隐患(情况?)
- 并发队列:可以用,用于需要等待完成的任务
- 自定义串行队列:在运行的串行队列中再同步派发百分之二百死锁!!
Dispatch_group
用于监控并发任务的完成。可以往dispatch group里添加需要监控的任务,任务可以来自不同的队列。添加与移除任务的方法有两种:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t downloadGroup = dispatch_group_create(); // 创建一个group
//1. 配对(必须!!)使用enter和leave,实际上是在递增/递减group未完成任务的count
dispatch_group_enter(downloadGroup); // 递增
dispatch_group_leave(downloadGroup); // 递减
//2\. 以异步方式提交一个block给特定派发队列,并让block持有group,block完成后释放group
dispatch_group_async(downloadGroup, queue, {^NSLog(@"blk0");});
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); //离开downloadGroup
当任务全部完成时,dispatch group可以通过同步/异步的方式通知:
-
同步:dispatch_group_wait,阻塞当前线程直到其中任务都完成或超时发生。小心阻塞主线程
-
异步:dispatch_group_notify,group中的任务都完成时通知,不会阻塞当前线程。
//1. 参数可以设置等待时间,有返回值,0代表group处理全部结束,1代表没有全部执行结束
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC); // 1秒
long result = dispatch_group_wait(downloadGroup, time); // 所以参数为DISPATCH_TIME_FOREVER时,只有完成才会返回
//2\. dispatch_group_notify 是一个异步方法,当group中无任务就会执行第三个参数传入的block
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(error);
}
});
}
Dispatch_apply
并发的执行不同的迭代,并发的全部迭代完成后才返回,函数声明:
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
dispatch_apply仅适用于特定情况下,如:需要用合适的步长迭代很大的集合。因为创建并运行线程也有开销,要考虑开销和收益的平衡。且提前优化代码会耗费巨大时间且降低可读性,优化应有针对性。
网友评论