Dispatch Semaphore VS Dispatch Group
Dispatch Group
Dispatch Group
在项目中比较常见,用于多任务多线程之间的协作。比如,使用两个队列分别请求不同的接口,等请求全部完成后,刷新页面。下面这段代码使用两个队列分别执行不同任务,当两个任务都完成后,通知主队列执行完成代码
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let group = DispatchGroup()
let q1 = DispatchQueue(label: "q1")
group.notify(queue: .main) {
print("Task All Done!")
}
q1.async(group: group) {
for i in 1 ... 5 {
print("q1 >>> \(i)")
}
}
let q2 = DispatchQueue(label: "q2")
q2.async(group: group) {
for i in 1 ... 5 {
print("q2 >>> \(i)")
}
}
// 输出
q1 >>> 1
q2 >>> 1
q1 >>> 2
q2 >>> 2
q2 >>> 3
q1 >>> 3
q2 >>> 4
q2 >>> 5
q1 >>> 4
q1 >>> 5
Task All Done!
需要注意的是,网络请求一般是异步的。所以不能 queue.async(group: group) {}
就完事了,而是要使用 Dispatch Group
的 enter
和 leave()
方法。如下:
group.enter()
request.start { request in
complete(true)
group.leave()
} failure: { request in
complete(false)
group.leave()
}
Dispatch Semaphore
简介
Dispatch Semaphore
像是一个闸机,每次发出一个信号(刷一次卡)就放过一段代码。比如下面这段代码,
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let semaphore = DispatchSemaphore(value: 0)
let serialQueue = DispatchQueue(label: "serial")
print("Task Start!")
serialQueue.async {
print("pause")
semaphore.wait()
print("continue step 1")
semaphore.wait()
print("continue step 2")
}
for i in 1 ... 2 {
DispatchQueue.global().asyncAfter(deadline: .now() + Double(i)) {
print(">>> step \(i) continued")
semaphore.signal()
}
}
// 输出
Task Start!
pause
>>> step 1 continued
continue step 1
>>> step 2 continued
continue step 2
每次发出一个信号 signal()
,就放开最前面的一处等待 wait()
。Dispatch Semaphore
具备类似 NSLock
的特性,可用于加锁解锁、线程同步等场景,而且性能很高,是比较推荐的一种同步方式。
初始化参数 value 的作用(重要)
初始化方法 DispatchSemaphore(value: 0)
里面有个 value
参数,它的含义非常重要。官方文档解释如下:
Passing zero for the value is useful for when two threads need to reconcile the completion of a particular event. Passing a value greater than zero is useful for managing a finite pool of resources, where the pool size is equal to the value.
意思是,如果要协调多线程特定事件的完成时,比如重要用法的多线程请求网络,把 value
设置为 0;如果要处理一串有限的任务,而且要保证处理顺序是特定的或者线程安全的(同步锁),就 value
设置为一个大于 0 的整数。
当 value
是 0 时,Dispatch Semaphore
的所有 wait()
不会在运行时被自动放过,只能在得到一个 signal()
时,解锁一个 wait()
;而当 value
> 0 时(假设为 x ),Dispatch Semaphore
的前 x 个 wait()
不用得到 signal()
就能被放过。通俗的讲 value
就是在初始化的时候就给它 x 个通行证。比如下面这段代码,
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let dispatchQueue = DispatchQueue(label: "com.liang.playground", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 0)
dispatchQueue.async {
semaphore.wait()
Thread.sleep(forTimeInterval: 4)
print("Sema block 1")
semaphore.signal()
}
dispatchQueue.async {
semaphore.wait()
Thread.sleep(forTimeInterval: 2)
print("Sema block 2")
semaphore.signal()
}
dispatchQueue.async {
semaphore.wait()
print("Sema block 3")
semaphore.signal()
}
dispatchQueue.async {
semaphore.wait()
print("Sema block 4")
semaphore.signal()
}
- 如果初始化的
value
给的是 0,那么将不会有任何输出。因为所有的wait()
都需要signal()
来解锁,但是初始化的时候是没有给它一个通行证的 - 如果初始化的
value
给的是 1,那么输出如下:
Sema block 1
Sema block 2
Sema block 3
Sema block 4
这是因为在初始化的时候给了程序一个通行证,所以第一个 wait()
便能顺利通过了。等待 4 秒后,会发出一个 signal()
,解锁第二个 wait()
…所有的异步都能顺利的执行了
- 如果初始化的
value
给的是 2,那么输出如下:
Sema block 2
Sema block 3
Sema block 4
Sema block 1
这是因为在初始化的时候给了程序连个通行证,这样前两个 wait()
就形同虚设了。但是第二个 block 会更快执行完,所以它先被打印出来。
重要用法
保证资源的线程安全
利用 Dispatch Semaphore
能够设置初始通行证数量(value
)的能力,可以保证一个资源在多线程操作时是线程安全的。
比如以下代码保证了变量 num
在被多个线程操作时,是线程安全的
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let sema = DispatchSemaphore(value: 1)
var num = 0
func addNum() {
sema.wait()
num += 1
print(">>> num = \(num)")
sema.signal()
}
DispatchQueue.global().async {
for i in 1 ... 20 {
addNum()
}
}
DispatchQueue.global().async {
for i in 1 ... 20 {
addNum()
}
}
// 输出
>>> num = 1
>>> num = 2
>>> num = 3
>>> num = 4
>>> num = 5
>>> num = 6
>>> num = 7
>>> num = 8
>>> num = 9
>>> num = 10
>>> num = 11
>>> num = 12
>>> num = 13
>>> num = 14
>>> num = 15
>>> num = 16
>>> num = 17
>>> num = 18
>>> num = 19
>>> num = 20
>>> num = 21
>>> num = 22
>>> num = 23
>>> num = 24
>>> num = 25
>>> num = 26
>>> num = 27
>>> num = 28
>>> num = 29
>>> num = 30
>>> num = 31
>>> num = 32
>>> num = 33
>>> num = 34
>>> num = 35
>>> num = 36
>>> num = 37
>>> num = 38
>>> num = 39
>>> num = 40
多线程网络请求
利用 Dispatch Semaphore
也能实现多个请求完成后通知主线程刷新 UI 的功能,代码像下面这样:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let semaphore = DispatchSemaphore(value: 0)
let serialQueue = DispatchQueue(label: "serial")
serialQueue.async {
// 3 requests
semaphore.wait()
semaphore.wait()
semaphore.wait()
DispatchQueue.main.async {
print(">>> update UI in main thead")
}
}
// request 1
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
print(">>> request 1 finished")
semaphore.signal()
}
// request 2
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
print(">>> request 2 finished")
semaphore.signal()
}
// request 3
DispatchQueue.global().asyncAfter(deadline: .now() + 5) {
print(">>> request 3 finished")
semaphore.signal()
}
// 输出
>>> request 1 finished
>>> request 2 finished
>>> request 3 finished
>>> update UI in main thead
总结
Dispatch Semaphore
通常用于单个事件的处理,而 Dispatch Group
通常用于多个事件的处理。
Dispatch Semaphore
更加简朴,性能上要优于 Dispatch Group
,我们应在代码中充分发挥利用它的特性,或者与 Dispatch Group
配合使用,完成一些比较复杂的逻辑流程。