基本概念
什么是 GCD ?
官方文档 说明如下:
Grand Central Dispatch( GCD )是异步启动任务的技术之一。此技术将开发者通常在应用程序中编写的线程管理代码在系统级别中实现。开发者通常要做的是定义要执行的任务并将它们添加到适当的 Dispatch Queue 中。GCD 就能创建所需的线程并计划执行任务。由于线程管理现在是系统的一部分,因此可以统一管理,也可执行任务,这样就比传统线程更有效率。
为什么要用 GCD ?
- GCD 会自动管理线程的生命周期,无需开发者编写任何线程管理代码
- GCD 可以通过将昂贵的计算任务放入后台处理,来改善应用的响应性能
- GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱
- GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力
任务与队列
任务
任务是指需要执行的操作,在 GCD 中就是 block 中的那一段代码。执行任务分为同步(sync) 和异步(async):
- 同步(sync)
- 同步添加任务到指定队列中,会一直等待任务执行结束,再执行后续程序
- 会阻塞线程
- 不会开启新线程
- 异步(async):
- 异步添加任务到指定队列中,不用等待任务执行结束,就可以立即返回执行后续程序
- 不会阻塞线程
- 可能会开启新线程
队列(DispatchQueue)
队列是一种特殊的线性表,用于存放任务,采用 FIFO(先进先出)的原则。在 GCD 中队列主要分为串行队列(Serial Dispatch Queue)和并行队列(Concurrent Dispatch Queue),两者都符合 FIFO 的原则,主要区别是执行的顺序,以及开启的线程数不同。
- 串行队列(Serial Dispatch Queue):使用一个线程,一个接着一个执行任务
- 并行队列(Concurrent Dispatch Queue):使用多个线程,同时执行多个任务
队列与线程
主队列
DispatchQueue(label: "queue").async {
print("queue: \(Thread.current)")
DispatchQueue.main.sync {
print("mainSync: \(Thread.current)")
}
DispatchQueue.main.async {
print("mainAsync1: \(Thread.current)")
}
DispatchQueue.main.async {
print("mainAsync2: \(Thread.current)")
}
}
打印:
queue: {number = 3, name = (null)}
mainSync: {number = 1, name = main}
mainAsync1: {number = 1, name = main}
mainAsync2: {number = 1, name = main}
可以看出主队列不管同步还是异步执行任务都是在主线程中的。
串行队列
DispatchQueue(label: "queue").async {
print("queue: \(Thread.current)")
let serialQueue = DispatchQueue(label: "serial")
serialQueue.sync {
print("serialSync1: \(Thread.current)")
}
serialQueue.sync {
print("serialSync2: \(Thread.current)")
}
serialQueue.async {
print("serialAsync1: \(Thread.current)")
}
serialQueue.async {
print("serialAsync2: \(Thread.current)")
}
}
打印1:
queue: {number = 3, name = (null)}
serialSync1: {number = 3, name = (null)}
serialSync2: {number = 3, name = (null)}
serialAsync1: {number = 3, name = (null)}
serialAsync2: {number = 3, name = (null)}
打印2:
queue: {number = 3, name = (null)}
serialSync1: {number = 3, name = (null)}
serialSync2: {number = 3, name = (null)}
serialAsync1: {number = 4, name = (null)}
serialAsync2: {number = 4, name = (null)}
可以看出串行队列同步执行任务的时候是在当前线程,而异步执行任务的时候可能在当前线程,也可能是新开一条线程来处理。
DispatchQueue(label: "queue").async {
print("queue: \(Thread.current)")
let serialQueue = DispatchQueue(label: "serial")
serialQueue.async {
Thread.sleep(forTimeInterval: 2)
print("serialAsync2: \(Thread.current)")
}
print("xx")
serialQueue.sync {
print("serialSync1: \(Thread.current)")
}
print("yy")
}
打印:
queue: {number = 3, name = (null)}
xx
2秒后:
serialAsync2: {number = 4, name = (null)}
serialSync1: {number = 3, name = (null)}
yy
可以看出在串行队列中异步任务虽然立即返回了,但当后续还有其他任务时,依然要等待异步任务执行完成后才能执行,造成线程阻塞。
并行队列
DispatchQueue(label: "queue").async {
print("queue: \(Thread.current)")
let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
concurrentQueue.sync {
print("concurrentSync1: \(Thread.current)")
}
concurrentQueue.sync {
print("concurrentSync2: \(Thread.current)")
}
concurrentQueue.async {
print("concurrentAsync1: \(Thread.current)")
}
concurrentQueue.async {
print("concurrentAsync2: \(Thread.current)")
}
}
打印:
queue: {number = 3, name = (null)}
concurrentSync1: {number = 3, name = (null)}
concurrentSync2: {number = 3, name = (null)}
concurrentAsync1: {number = 3, name = (null)}
concurrentAsync2: {number = 3, name = (null)}
可以看出并行队列同步执行任务时候是在当前线程,异步任务也可能是在当前线程。
DispatchQueue(label: "queue").async {
print("queue: \(Thread.current)")
let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
concurrentQueue.sync {
Thread.sleep(forTimeInterval: 2)
print("concurrentSync1: \(Thread.current)")
}
print("xx")
concurrentQueue.async {
Thread.sleep(forTimeInterval: 2)
print("concurrentAsync1: \(Thread.current)")
}
print("yy")
concurrentQueue.async {
Thread.sleep(forTimeInterval: 2)
print("concurrentAsync2: \(Thread.current)")
}
print("zz")
concurrentQueue.sync {
print("concurrentSync2: \(Thread.current)")
}
}
打印:
queue: {number = 3, name = (null)}
2秒后:
concurrentSync1: {number = 3, name = (null)}
xx
yy
zz
concurrentSync2: {number = 3, name = (null)}
4秒后:
concurrentAsync2: {number = 4, name = (null)}
concurrentAsync1: {number = 3, name = (null)}
可以看出并行队列同步执行任务的时候也会阻塞当前线程,异步执行任务则会在当前线程或者新开线程执行而且不会阻塞线程。
总结
- 主队列的任务都在主线程执行,在主线程上调用主队列同步执行任务会造成死锁
- 同步执行任务都在当前线程
- 异步执行任务也可能会在当前线程,也可能新开线程(串行队列会新开一条,并行队列会新开多条)
- 串行队列执行任务都是一个接一个执行的,就算是异步任务,也只是提前返回,后续任务也要等该异步任务执行完成后才能继续
- 并行队列异步执行任务的时候是开启多个线程并发执行多个任务,不会造成线程阻塞
主队列(main) | 串行队列(serial) | 并行队列(concurrent) | |
---|---|---|---|
同步(sync) | 主线程 | 当前线程 | 当前线程 |
异步(async) | 主线程 | 当前线程/新线程(一个) | 当前线程/新线程(多个) |
关于同步执行任务都在当前线程,官方文档 描述如下:
As a performance optimization, this function executes blocks on the current thread whenever possible, with one obvious exception. Specifically, blocks submitted to the main dispatch queue always run on the main thread.
其他多线程编程技术
GCD 的几种操作
Group
打包几个异步任务,等待它们都执行完之后发出通知。
func group() {
let group = DispatchGroup()
let queue = DispatchQueue(label: "groupQueue", qos: .default, attributes: .concurrent)
queue.async(group: group) {
Thread.sleep(forTimeInterval: 2)
print("1111")
}
queue.async(group: group) {
Thread.sleep(forTimeInterval: 5)
print("2222")
}
group.notify(queue: queue) {
print("over")
}
print("xxxx")
}
打印:
xxxx
1111
2222
over
barrier
栅栏任务的主要特性是可以对队列中的任务进行阻隔,执行栅栏任务时,它会先等待队列中已有的任务全部执行完成,然后它再执行,在它之后加入的任务也必须等栅栏任务执行完后才能执行。
func barrier() {
let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
concurrentQueue.async {
Thread.sleep(forTimeInterval: 2)
print("concurrentAsync1: \(Thread.current)")
}
concurrentQueue.async {
Thread.sleep(forTimeInterval: 2)
print("concurrentAsync2: \(Thread.current)")
}
concurrentQueue.async(flags: .barrier) {
Thread.sleep(forTimeInterval: 4)
print("栅栏")
print("barTask: \(Thread.current)")
}
concurrentQueue.async {
Thread.sleep(forTimeInterval: 2)
print("concurrentAsync3: \(Thread.current)")
}
concurrentQueue.async {
Thread.sleep(forTimeInterval: 2)
print("concurrentAsync4: \(Thread.current)")
}
}
//另一种写法
func barrier() {
let barTask = DispatchWorkItem(flags: .barrier) {
Thread.sleep(forTimeInterval: 4)
print("栅栏")
print("barTask: \(Thread.current)")
}
let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
concurrentQueue.async {
Thread.sleep(forTimeInterval: 2)
print("concurrentAsync1: \(Thread.current)")
}
concurrentQueue.async {
Thread.sleep(forTimeInterval: 2)
print("concurrentAsync2: \(Thread.current)")
}
concurrentQueue.async(execute: barTask)
// barTask.wait()
concurrentQueue.async {
Thread.sleep(forTimeInterval: 2)
print("concurrentAsync3: \(Thread.current)")
}
concurrentQueue.async {
Thread.sleep(forTimeInterval: 2)
print("concurrentAsync4: \(Thread.current)")
}
}
打印:
concurrentAsync1: {number = 3, name = (null)}
concurrentAsync2: {number = 4, name = (null)}
栅栏
barTask: {number = 4, name = (null)}
concurrentAsync3: {number = 4, name = (null)}
concurrentAsync4: {number = 5, name = (null)}
semaphore
DispatchSemaphore,通常称作信号量,顾名思义,它可以通过计数来标识一个信号,这个信号怎么用呢,取决于任务的性质。通常用于对同一个资源访问的任务数进行限制。例如,控制同一时间写文件的任务数量、控制端口访问数量、控制下载任务数量等。
func semaphore() {
let queue = DispatchQueue.global()
let sem = DispatchSemaphore(value: 2)
let _ = sem.wait(timeout: DispatchTime.now() + 5)
queue.async {
Thread.sleep(forTimeInterval: 2)
print("task 1 over")
sem.signal()
}
sem.wait()
queue.async {
Thread.sleep(forTimeInterval: 2)
print("task 2 over")
sem.signal()
}
sem.wait()
queue.async {
Thread.sleep(forTimeInterval: 2)
print("task 3 over")
sem.signal()
}
}
打印:
task 1 over
task 2 over
2秒后:
task 3 over
concurrentPerform
并行队列利用多个线程执行任务,可以提高程序执行的效率。而迭代任务可以更高效地利用多核性能,它可以利用 CPU 当前所有可用线程进行计算(任务小也可能只用一个线程)。如果一个任务可以分解为多个相似但独立的子任务,那么迭代任务是提高性能最适合的选择。
使用 concurrentPerform
方法执行迭代任务,迭代任务的后续任务需要等待它执行完成才会继续。
func concurrentPerform() {
var reslut = [Int]()
DispatchQueue.global().async {
DispatchQueue.concurrentPerform(iterations: 100, execute: { (index) in
if index % 13 == 0 {
print("current: \(Thread.current)")
DispatchQueue.main.async {
reslut.append(index)
}
}
})
DispatchQueue.main.async {
print(reslut)
}
}
}
打印:
current: {number = 5, name = (null)}
current: {number = 6, name = (null)}
current: {number = 3, name = (null)}
current: {number = 4, name = (null)}
current: {number = 4, name = (null)}
current: {number = 3, name = (null)}
current: {number = 5, name = (null)}
current: {number = 6, name = (null)}
[39, 13, 0, 26, 65, 91, 78, 52]