GCD 是操作系统层级的概念,他给用户提供了操作线程的 API
任务派发中心,内部实现原理是有了 FIFO 分发队列,GCD 源码
使用 GCD 用户不需要直接操作繁琐的 thread,线程池由系统管理,用户只需要维护分发队列,向分发队列中放 task 就可以了。
直接使用线程可能会引发的一个问题是,如果你的代码和所基于的框架代码都创建自己的线程时,那么活动的线程数量有可能以指数级增长,每个线程都会消耗一些内存和内核资源。
主要原理
主要理解三个概念
- queue: 管理任务的队列,确定任务派发方式
- task: 用户自定义需要执行的 task (代码段)
- thread: GCD 根据 queue 定义的方式,将 task 派发给线程池中的指定线程(thread 不需要自己创建)
- queue
- 串行队列,所有任务都在同一个 thread 上一个接着一个的执行
- 特例:主队列 main_queue,所有 task 在 mainThread 中执行
- 并发队列,任务可以在多个 thread 中执行没有固定执行顺序
- 特例:global 队列
- 串行队列,所有任务都在同一个 thread 上一个接着一个的执行
1 | concurrent |
同步异步执行任务
- sync 所有任务要在一个 thread 中执行,一个接着一个
- async 具备开启线程的能力,可以在多个thread 中并发执行任务
thread
- 使用 GCD,不用再直接跟线程打交道了,只需要向队列中添加代码块(task)即可,GCD 在后端管理着一个线程池。
- 作为开发者可以将工作考虑为一个队列,而不是一堆线程,这种并行的抽象模型更容易掌握和使用。
- 根据👆图 task 真正的并发只有 右下角的才能出现,并发队列中异步执行任务
P.S.:async 执行 task的时候 thread 的个数跟 task 的个数没有关系(现在 iOS系统是开辟 6个 thread,以前低版本的是 3个,thread 开辟太多,会用掉大量内存)
接下来说一些 GCD 接口,根据接口分析原理
分发队列
队列的创建
- GCD 公开有 5 个不同的全局队列:
- 运行在主线程中的 main queue(串行队列)
- global 队列,3 个不同优先级的后台队列(并发队列)
- 以及一个优先级更低的后台队列(用于 I/O)(并发队列)
得到系统的全局队列
1 | dispatch_queue_main_t mainDispatchQueue = dispatch_get_main_queue(); |
- 自定义队列:串行或者并行队列。自定义队列非常强大,在自定义队列中被调度的所有 block 最终都将被放入到系统的全局队列中和线程池中。
1 | dispatch_queue_t queue |
创建队列并设置优先级
- 获取全局 global 系统队列时传递优先级
- dipatch_queue_attr_make_with_qos_class
- dispatch_set_target_queue
使用 dispatch_queue_attr_t
属性设置优先级
1 | dispatch_queue_t queue; |
DISPATCH_QUEUE_PRIORITY_HIGH | QOS_CLASS_USER_INITIATED |
DISPATCH_QUEUE_PRIORITY_DEFAULT | QOS_CLASS_DEFAULT |
DISPATCH_QUEUE_PRIORITY_LOW | QOS_CLASS_UTILITY |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | QOS_CLASS_BACKGROUND |
- QOS_CLASS_USER_INTERACTIVE 指定为该QOS class的队列负责执行与用户交互相关的任务,比如动画、事件处理、更新UI等,所以有最高优先级。该优先级的队列应该只限于做与用户交互相关的任务,所以在上面优先级的宏定义中并没有将其暴露出来。(推测主队列优先级是这个)
- QOS_CLASS_USER_INITIATED 指定为该QOS class的队列用来执行那些会阻碍用户使用你的App的任务,所以优先级也很高。
- QOS_CLASS_DEFAULT 默认优先级。
- QOS_CLASS_UTILITY 指定为该QOS class的队列用于执行那些用户不需要立即得到结果的任务,所以优先级相对较低。(long-running computations, I/O, networking or continuous data feeds.)
- QOS_CLASS_BACKGROUND 指定为该QOS class的队列用于执行维护或清理等任务,用户不需要关心其结果。
设置目标队列
dispatch_set_target_queue 相关注释说明
1
2
3
4
5
6 * When no quality of service class and relative priority is specified for a
* dispatch queue at the time of creation, a dispatch queue's quality of service
* class is inherited from its target queue. The dispatch_get_global_queue()
* function may be used to obtain a target queue of a specific quality of
* service class, however the use of dispatch_queue_attr_make_with_qos_class()
* is recommended instead.
- 将自定义 queue 中的 task 都将被放入到系统的全局队列和线程池中:默认情况下会把开发者创建的队列放入到默认优先级的全局队列中。但是也可以给自定义的队列设置一个目标队列
- 改变自定义 queue 的优先级:让其执行优先级与该目标队列的执行优先级一致。
- 改变 queue 中 task 执行的方式:不仅能改变优先级,如果一个队列是并行的,但是其目标队列是串行的,那么实际上这个队列也会转换为串行队列。再者,不同串行队列中的任务是可以同时执行的,如果把这些串行队列的目标队列都设置为同一个串行队列,那这些串行队列中的任务将不会并行执行。
1 | * @param object |
dispatch_set_target_queue:可以设置优先级,也可以设置队列层级体系,比如让多个串行和并行队列在统一一个串行队列里串行执行
dispatch_set_target_queue使用后的效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 func setTarget() {
let serialQueue = DispatchQueue(label: "serialQueue")
let aQueue = DispatchQueue(label: "concurrent.queue.0", attributes: .concurrent, target: serialQueue)
let bQueue = DispatchQueue(label: "concurrent.queue.1", attributes: .concurrent, target: serialQueue)
let alabel = aQueue.label
let blabel = bQueue.label
aQueue.async {
print("\(alabel): 1")
sleep(3)
}
bQueue.async {
print("\(blabel): 2")
sleep(1)
}
bQueue.async {
print("\(blabel): 3")
sleep(2)
}
}
/*
concurrent.queue.0: 1
concurrent.queue.1: 2
concurrent.queue.1: 3
*/
func nosetTarget() {
let aQueue = DispatchQueue(label: "concurrent.queue.0", attributes: .concurrent)
let bQueue = DispatchQueue(label: "concurrent.queue.1", attributes: .concurrent)
let alabel = aQueue.label
let blabel = bQueue.label
aQueue.async {
print("\(alabel): 1")
sleep(3)
}
bQueue.async {
print("\(blabel): 2")
sleep(1)
}
bQueue.async {
print("\(blabel): 3")
sleep(2)
}
}
/*
concurrent.queue.0: 1
concurrent.queue.1: 3
concurrent.queue.1: 2
*/
GCD 任务调度
1 | public func sync(execute workItem: DispatchWorkItem) |
只执行一次 task
dispatch_once 只执行一次指定的block。它的性能要比@synchronized要好。@synchronized每一次都要先获取锁,而dispatch_once使用一个token标识代码是否执行过。
1 | static dispatch_once_t onceToken; |
在Swift 3.0中这个函数被废弃了,但是可以使用懒加载的全局变量或静态变量,也能保证线程安全。
1 | let onceTask: String = { |
添加栅栏函数
Dispatch Barrier解决多线程多读单写同一个资源发生死锁问题
同步队列中不会出现这个问题
在并发队列中,如果添加了 .barrier
task 那么在 .barrier
task 之前添加的任务所有任务执行之前都会有任务执行,在全局并发队列和串行队列上,效果和dispatch_sync一样
有两个接口
1 | //Submits a barrier block object for execution and waits until that block completes. |
1 | func barrier() { |
在实际开发中一个很好的使用 barrier 控制多读单写的例子,就是重写 getter,setter 方法
多读单写特点:
- 读者与读者并发
- 读者与写者互斥
- 写者与写者互斥
- 要在 concurrent queue 中执行
dispatch_barrier_async
- 重写需要加锁数据的 getter,setter 方法
- getter 方法需要立即得到数据所以使用
sync
- setter 方法
async
,因为读的过程不需要立即得到结果
- getter 方法需要立即得到数据所以使用
1 | // 对于变量 |
添加 group 通知
dispatch groups是专门用来监视多个异步任务。dispatch_group_t实例用来追踪不同队列中的不同任务。
当group里所有事件都完成GCD API有两种方式发送通知
第一种是dispatch_group_wait,会阻塞当前进程,等所有任务都完成或等待超时
第二种方法是使用dispatch_group_notify,异步执行闭包,不会阻塞。
1 | // notify 任务组完成后,执行 notify 要做的事,notify不阻塞线程 |
如果放入组里面的任务内部没有嵌套任务,那么一切正常
可是如果组里面的 task 内部异步任务呢?eg task0{ async(task1) }
这个时候我想,task1 异步回调执行完,才算task0 这个组任务完成该怎么做?
1 | public func enter() |
实际使用group的例子
1 | let downloadGroup = DispatchGroup() |
挂起/恢复队列
1 | queue.suspend()// 当前 isExcuting = true 的operation 不会听通知 |
这里挂起不会暂停正在执行的block
dispatch_apply进行快速迭代
1 | func apply() { |
dispatch_apply
- 在并发队列中使用
- 将 task 追加到队列中
- 所有 task 并发执行完以后同步执行后面的打印
- dispatch_apply 添加的 task 是异步并发执行,外部是同步执行
用dispatch_apply替代对数组等的for循环,把这些block放到并行队列中可以提高执行效率。
1 | P.S. apply 主要针对于大量并发的时候使用,少量的时候没有必要 |
Dispatch Semaphore
使用变量管理多线程的同步方法
1 | dispatch_semaphore_t dispatch_semaphore_create(long value); |