iOS 多线程研究之GCD

557 阅读5分钟

任务 && 队列

同步异步的区别是在于 是否需要等待 当前任务 执行结束,以及是否具备开启新线程的能力。

任务类型特性
同步会等待当前的任务执行完成后,才执行后面的任务(会阻塞后面的任务),不能开启新的线程
异步不会等待当前的任务执行完成,具备开启新线程的能力

串行并行的主要区别在于 同时执行任务的数量不同,以及开启线程的数量。

队列类型特性
串行不可开启线程,任务一个接一个执行(只能在当前线程中执行)
并行可以开启多个线程,同时执行多个任务
主队列(串行)和串行队列一样,只是 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 的标记,将标记前后的任务隔离开,就像一个栅栏一样。故称栅栏函数

多读单写

使用并发队列,写操作时,使用异步栅栏函数。读操作,使用同步。

这个在 RNRegisterModuleClassGetModuleClasses 中使用到了。

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

  1. 当所有的 enterleave 的时候,执行 notify 的代码。enter 相当于 group 任务数量加1,leave 表示任务数量减1。
  2. 或者直接把异步任务加到对应的 group 中,当所有任务执行完成后,执行 notify 的代码。

wait

等待当前的线程执行完成后,才会执行后面的代码。

信号量(semaphore([ˈseməfɔː(r)]))

很多人读不来这个英语。

create

初始化一个信号量。

signal

信号量 +1。

wait

信号量 -1。

用途

  1. 异步转同步
  2. 加锁(信号量小于0时,后面的代码就无法执行了)

关于回调地狱的思考

promisesRxSwift等三方库中都可以。

异步转同步

  1. 锁/信号量(各种锁)
  2. 同步任务
  3. group

demo

上面的代码均放在 github 上面了,需要的可以自行下载研究。

官方文档

概述

  1. 提交到某一个队列的任务最终会被完全由系统管理的线程池中执行。不能保证最终被提交到哪个线程中执行,但是可以保证一定会在未来的某个时刻被执行。当多个队列中有任务被执行时,系统会自动创建额外的线程来并发的执行这些任务,当队列被清空后,这些线程也会由系统自动释放。

内存管理

队列的内在管理是通过调用 dispatch_retain()dispatch_release()。被提交到队列中的等待执行的任务也持有队列的一个引用,直到他被执行完成。一旦一个队列中所有的引用都被释放的时候,这个队列也就会被系统释放。

全局队列

提供了一个优先级的桶,系统管理的线程池。系统将会根据需要决定池子的大小,系统会尽可能的保证高并发性,并且在线程都被阻塞的情况下会自动创建新的线程。

全局队列是一个共享资源,每个人都有责任不向里面无边界的提交任务,特别是阻塞任务时,这个可能会造成线程爆炸。

任务被提交到全局队列是没有什么顺序可言,而且会被并发执行。

不可以被修改,通过 dispatch_suspend(), dispatch_resume(), dispatch_set_context() 是无效果的。

串型队列

任务将以 FIFO 的顺序执行。一个串行队列一次只能执行一个任务,但独立的串行队列可以并发执行他们的任务,以互相尊重的方式。

主队列

为了执行提交互主队列中的任务,必须要调用 dispatch_main(), NSApplicationMain(),或者开启一个 CFRunloop 循环。

不可以被修改,而且主队列是一个没有副作用的队列。

细节点

  1. 如果我在并行队列中,像主队列中提交了一个异步任务,但是这个异步任务中包含耗时操作,由于会切回主线程操作,所以还是会造成卡顿。
  2. enterleave 必须配对出现。

参考资料

GCD 源码

iOS 多线程:『GCD』详尽总结

GCD中的队列 和 线程之间的关系