任务 && 队列
同步异步的区别是在于 是否需要等待 当前任务 执行结束,以及是否具备开启新线程的能力。
| 任务类型 | 特性 |
|---|---|
| 同步 | 会等待当前的任务执行完成后,才执行后面的任务(会阻塞后面的任务),不能开启新的线程 |
| 异步 | 不会等待当前的任务执行完成,具备开启新线程的能力 |
串行并行的主要区别在于 同时执行任务的数量不同,以及开启线程的数量。
| 队列类型 | 特性 |
|---|---|
| 串行 | 不可开启线程,任务一个接一个执行(只能在当前线程中执行) |
| 并行 | 可以开启多个线程,同时执行多个任务 |
| 主队列(串行) | 和串行队列一样,只是 UI 相关操作必须在此队列 |
| 全局队列(并行) |
并行异步
最难以被理解的一种方式。把异步任务提交到并行队列中,那么完全没有办法确定执行顺序了。
print("begin")
DispatchQueue.global().async {
let queue = DispatchQueue(label: "xxx", attributes: .concurrent)
(0 ..< 10).forEach { i in
queue.async {
print(i)
}
}
// 这里改为 async/barrier_async 结果又会不同
queue.sync {
Thread.sleep(forTimeInterval: 1)
print("我是分隔线")
}
(10 ..< 20).forEach { i in
queue.async {
print(i)
}
}
// 这句代码就相当于把一个打印 20-29 的同步任务提交到了全局队列中
// 他的打印顺序是确定的,但是由于队列中不止它一个任务
// 最后会导致打印结果无法确定
(20 ..< 30).forEach { i in
print(i)
}
}
print("end")
打印结果较多,详情可以看 demo 中的混合研究按钮的执行结果。
线程情况
| 任务类型 | 串行 | 并行 | 主 | 全局 |
|---|---|---|---|---|
| 同步 | 不开启新线程 | 不开启新线程 | 死锁 | 不开启新线程 |
| 异步 | 开启一个新线程 | 根据需求可开启多个新线程 | 切回主线程执行 | 根据需求可开启多个新线程 |
从这里其实可以看出队列和线程其实没啥直接的关系,队列只是会把任务在某个时间点提交给线程执行。
barrier
DispatchWorkItem(flags: .barrier, block: {}))
其实就是把任务打个 barrier 的标记,将标记前后的任务隔离开,就像一个栅栏一样。故称栅栏函数。
多读单写
使用并发队列,写操作时,使用异步栅栏函数。读操作,使用同步。
这个在 RN 中 RegisterModuleClass 及 GetModuleClasses 中使用到了。
after
在指定时间将一个异步任务添加到指定队列。如:在主队列 2s 时添加一个异步任务。
print(1)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("2s 后执行")
}
print(2)
sleep(3)
print(3)
由于主队列中的任务并未执行完成,故提交的异步任务需要等待主队列任务先执行完成。所以最后的结果为:先打印1,2,隔3s后打印3,2s 后执行。
apply
略
group
enter/leave && notify
- 当所有的
enter都leave的时候,执行notify的代码。enter相当于group任务数量加1,leave表示任务数量减1。 - 或者直接把异步任务加到对应的
group中,当所有任务执行完成后,执行notify的代码。
wait
等待当前的线程执行完成后,才会执行后面的代码。
信号量(semaphore([ˈseməfɔː(r)]))
很多人读不来这个英语。
create
初始化一个信号量。
signal
信号量 +1。
wait
信号量 -1。
用途
- 异步转同步
- 加锁(信号量小于0时,后面的代码就无法执行了)
关于回调地狱的思考
promises、RxSwift等三方库中都可以。
异步转同步
- 锁/信号量(各种锁)
- 同步任务
group
demo
上面的代码均放在 github 上面了,需要的可以自行下载研究。
官方文档
概述
- 提交到某一个队列的任务最终会被完全由系统管理的线程池中执行。不能保证最终被提交到哪个线程中执行,但是可以保证一定会在未来的某个时刻被执行。当多个队列中有任务被执行时,系统会自动创建额外的线程来并发的执行这些任务,当队列被清空后,这些线程也会由系统自动释放。
内存管理
队列的内在管理是通过调用 dispatch_retain() 和 dispatch_release()。被提交到队列中的等待执行的任务也持有队列的一个引用,直到他被执行完成。一旦一个队列中所有的引用都被释放的时候,这个队列也就会被系统释放。
全局队列
提供了一个优先级的桶,系统管理的线程池。系统将会根据需要决定池子的大小,系统会尽可能的保证高并发性,并且在线程都被阻塞的情况下会自动创建新的线程。
全局队列是一个共享资源,每个人都有责任不向里面无边界的提交任务,特别是阻塞任务时,这个可能会造成线程爆炸。
任务被提交到全局队列是没有什么顺序可言,而且会被并发执行。
不可以被修改,通过 dispatch_suspend(), dispatch_resume(), dispatch_set_context() 是无效果的。
串型队列
任务将以 FIFO 的顺序执行。一个串行队列一次只能执行一个任务,但独立的串行队列可以并发执行他们的任务,以互相尊重的方式。
主队列
为了执行提交互主队列中的任务,必须要调用 dispatch_main(), NSApplicationMain(),或者开启一个 CFRunloop 循环。
不可以被修改,而且主队列是一个没有副作用的队列。
细节点
- 如果我在并行队列中,像主队列中提交了一个异步任务,但是这个异步任务中包含耗时操作,由于会切回主线程操作,所以还是会造成卡顿。
enter和leave必须配对出现。
参考资料