Swift中的GCD

1.任务

在 Swift 中往GCD队列里提交的任务,有两种常见封装方法。

1.闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// GCD
let q1 = DispatchQueue.init(label: "serial") // 默认串行
let q2 = DispatchQueue.init(label: "concurrent", attributes: .concurrent) // 指定为并发
print("start")
q2.async { // 使用闭包提交任务
Thread.sleep(forTimeInterval: 2)
print("++1 \(Thread.current)")
}
q2.async {
Thread.sleep(forTimeInterval: 1)
print("++2 \(Thread.current)")
}
q2.async {
print("++3 \(Thread.current)")
}
print("end")

2.DispatchWorkItem

1
2
3
4
5
6
7
8
9
10
11
let q = DispatchQueue.init(label: "concurrent", attributes: .concurrent) // 指定为并发
// 使用WorkItem提交任务
let work = DispatchWorkItem.init(block:{
print("++1: item run on \(Thread.current)")
DispatchQueue.global().async {
print("++2: item run on \(Thread.current)")
}
})
//work.perform() //在当前线程执行任务
// 执行任务
q.async(execute: work)
1.属性

在构造WorkItem时,可以设置优先级与执行策略。

1
public init(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], block: @escaping @convention(block) () -> Void)
  • qos

与OC中类似,优先级从高到低包括了:

1
2
3
4
5
userInteractive //用于涉及到UI交互的场景
userInitiated //用于用户触发的、需要立刻返回结果的任务
default //默认值
utility //用于不需要立刻返回结果的任务
background //用于非用户触发的、不可见的任务
  • flags
1
2
3
4
5
6
barrier //栅栏,与OC中的一样
assignCurrentContext //使用所在队列或者线程的属性设置,如优先级
detached //不使用所在队列或者线程的属性设置
noQoS //不指定优先级
inheritQoS //设置本任务的优先级 <= 当前队列或线程的优先级
enforceQoS //设置本任务的优先级 >= 当前队列或线程的优先级
2.API
1.perform()

使任务直接在当前的线程中执行。

1
2
3
4
5
6
7
8
9
10
11
let work = DispatchWorkItem.init(block:{
print("++1: item run on \(Thread.current)")
DispatchQueue.global().async {
print("++2: item run on \(Thread.current)")
}
})
work.perform()

//输出日志:
//++1: item run on <NSThread: 0x600001cf41c0>{number = 1, name = main}
//++2: item run on <NSThread: 0x600001cfa240>{number = 3, name = (null)}
2.wait()

阻塞当前线程,待当前任务完成后,再继续执行后续代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
let work = DispatchWorkItem.init {
sleep(1)
print("++working.. \(Thread.current)")
}
DispatchQueue.global().async(execute: work)
print("++before wait on \(Thread.current)")
work.wait()
print("++after wait on \(Thread.current)")

//输出日志:
//++before wait on <NSThread: 0x600001b08440>{number = 1, name = main}
//++working.. <NSThread: 0x600001b023c0>{number = 6, name = (null)}
//++after wait on <NSThread: 0x600001b08440>{number = 1, name = main}

代码执行时,先输出before,等待1秒,再连续输出working与after。

这里涉及到了线程间的通信,wait会阻塞其所在的主线程,待异步线程中的任务完成后,才继续主线程的后续代码。

当然,执行到wait()时,如果任务已经完成,则会直接执行后续代码。比如上面的示例中改用sync同步提交任务时:

1
2
3
4
5
6
7
8
9
10
11
12
13
let work = DispatchWorkItem.init {
sleep(1)
print("++working.. \(Thread.current)")
}
DispatchQueue.global().sync(execute: work)
print("++before wait on \(Thread.current)")
work.wait()
print("++after wait on \(Thread.current)")

//输出日志:
//++working.. <NSThread: 0x6000019f40c0>{number = 1, name = main}
//++before wait on <NSThread: 0x6000019f40c0>{number = 1, name = main}
//++after wait on <NSThread: 0x6000019f40c0>{number = 1, name = main}

再次执行时,到sync所在行时不会开启新线程,即在主线程执行work并等待1秒,随后连续输出before与after。看上去wait并没效果,是因为执行到wait时,work任务已经完成,所以直接跳过并执行after去了。

3.notify()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let q1 = DispatchQueue.init(label: "q1")
let q2 = DispatchQueue.init(label: "q2")
let work = DispatchWorkItem.init(block: {
print("++work on \(Thread.current)")
sleep(2)
})
// 监听通知
work.notify(queue: q2) {
print("notify on \(Thread.current)")
}
q1.async(execute: work)
print("finished on \(Thread.current)")

//输出日志:
//++work on <NSThread: 0x600000785f40>{number = 3, name = (null)}
//++finished on <NSThread: 0x60000078c180>{number = 1, name = main}
//++notify on <NSThread: 0x6000007889c0>{number = 7, name = (null)}

notify也涉及到线程间的通信,收到任务完成通知后,可以在另一队列中执行其他任务。

需要指出的是,notify中的任务会异步地执行,因此有机会开辟新线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let q1 = DispatchQueue.init(label: "q1")
let work = DispatchWorkItem.init(block: {
print("++work on \(Thread.current)")
sleep(2)
})
// 通知
work.notify(queue: q1) {
print("++notify on \(Thread.current)")
}
q1.sync(execute: work)
print("++finished on \(Thread.current)")

//输出日志:
//++work on <NSThread: 0x600003ee0200>{number = 1, name = main}
//++notify on <NSThread: 0x600003ee8ac0>{number = 3, name = (null)}
//++finished on <NSThread: 0x600003ee0200>{number = 1, name = main}

从日志可以看到,通过sync方式提交的任务是在主线程上执行的,任务完成后 notify 却是在子线程中执行的,可以推断这里 notify 是异步执行的,开辟了新线程。

2.任务组Group

1
2
public func async(group: DispatchGroup, execute workItem: DispatchWorkItem)
public func async(group: DispatchGroup? = nil, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @escaping @convention(block) () -> Void)

DispatchGroup只支持异步的提交任务,任务可以是闭包或 WorkItem。

1.notify()

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
// Group
let q = DispatchQueue.init(label: "q", attributes: .concurrent)
let group = DispatchGroup.init()
print("++start")
q.async(group: group){
sleep(2)
print("++1")
}
q.async(group: group){
print("++2")
}
q.async(group: group){
print("++3")
}
group.notify(queue: DispatchQueue.main) {
print("++finished")
}
print("++end")

//输出日志:
//++start
//++2
//++3
//++end
//++1
//++finished

2.enter/leave

与OC中一样,当提交到Group中的是异步任务时,notify会先于异步任务执行。

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
let q = DispatchQueue.init(label: "q", attributes: .concurrent)
let group = DispatchGroup.init()
print("++start")
q.async(group: group){
DispatchQueue.global().async {
sleep(2)
print("++1")
}
}
q.async(group: group){
print("++2")
}
q.async(group: group){
DispatchQueue.global().async {
print("++3")
}
}
group.notify(queue: DispatchQueue.main) {
print("++finished")
}
print("++end")

//输出日志:
//++start
//++end
//++2
//++3
//++finished
//++1

日志显示,notify先于异步任务1执行了。

解决这种问题,依然可以像OC一样,使用enterleave

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
let q = DispatchQueue.init(label: "q", attributes: .concurrent)
let group = DispatchGroup.init()
print("++start")
q.async(group: group){
group.enter() //改了这里
DispatchQueue.global().async {
sleep(2)
print("++1")
group.leave() //改了这里
}
}
q.async(group: group){
print("++2")
}
q.async(group: group){
group.enter() //改了这里
DispatchQueue.global().async {
print("++3")
group.leave() //改了这里
}
}
group.notify(queue: DispatchQueue.main) {
print("++finished")
}
print("++end")

//输出日志:
//++start
//++2
//++end
//++3
//++1
//++finished

3.wait()

wait会阻塞其所在线程,待Group中的任务都完成后,继续执行wait后面的代码。

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
let q = DispatchQueue.init(label: "q", attributes: .concurrent)
let group = DispatchGroup.init()
print("++start")
q.async(group: group){
sleep(2)
print("++1")
}
q.async(group: group){
print("++2")
}
q.async(group: group){
print("++3")
}
group.wait()
print("++end")
group.notify(queue: DispatchQueue.main) {
print("++finished")
}

//输出日志:
//++start
//++2
//++3
//++1
//++end
//++finished

3.栅栏barrier

  • 栅栏会分割队列中的任务,其前面的先执行,其后面的后执行。
  • 栅栏中的任务会在同一线程中执行。
  • 栅栏外的其他任务根据提交方式,可能会在不同线程中执行。

1.配合block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let q = DispatchQueue.init(label: "q", attributes: .concurrent)
for index in 0..<10 {
if index >= 5 && index < 7 {
q.async(flags: .barrier) { // 栅栏
print("barrier \(index), on \(Thread.current)")
}
}else{
q.async(){
print("\(index), on \(Thread.current)")
}
}
}

//输出日志:
//0, on <NSThread: 0x600003e92280>{number = 6, name = (null)}
//2, on <NSThread: 0x600003e9e300>{number = 5, name = (null)}
//1, on <NSThread: 0x600003e99a40>{number = 7, name = (null)}
//4, on <NSThread: 0x600003e99a40>{number = 7, name = (null)}
//3, on <NSThread: 0x600003e92280>{number = 6, name = (null)}
//barrier 5, on <NSThread: 0x600003e92280>{number = 6, name = (null)}
//barrier 6, on <NSThread: 0x600003e92280>{number = 6, name = (null)}
//9, on <NSThread: 0x600003e9e300>{number = 5, name = (null)}
//8, on <NSThread: 0x600003e92280>{number = 6, name = (null)}
//7, on <NSThread: 0x600003e9ea40>{number = 3, name = (null)}

2.配合workItem

DispatchWorkItem 中有个 flags 属性,barrier就是其枚举值之一。

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
let q = DispatchQueue.init(label: "concurrent", attributes: .concurrent)
for index in 0 ..< 10 {
if index >= 5 && index < 7 {
let work = DispatchWorkItem.init(flags: .barrier) { // 栅栏
print("barrier \(index), on \(Thread.current)")
}
q.async(execute: work)
}else{
q.async {
print("\(index), on \(Thread.current)")
}
}
}

//输出的日志:
//0, on <NSThread: 0x600001fe8600>{number = 6, name = (null)}
//1, on <NSThread: 0x600001fe8680>{number = 5, name = (null)}
//2, on <NSThread: 0x600001fe9200>{number = 7, name = (null)}
//4, on <NSThread: 0x600001fe8600>{number = 6, name = (null)}
//3, on <NSThread: 0x600001fe8640>{number = 8, name = (null)}
//barrier 5, on <NSThread: 0x600001fe8640>{number = 8, name = (null)}
//barrier 6, on <NSThread: 0x600001fe8640>{number = 8, name = (null)}
//8, on <NSThread: 0x600001fe8680>{number = 5, name = (null)}
//9, on <NSThread: 0x600001fe8600>{number = 6, name = (null)}
//7, on <NSThread: 0x600001fe8640>{number = 8, name = (null)}

根据栅栏的特性,可以将其用作读写锁:

读的任务放在并发队列中,同一时刻允许多读;

写的任务放在栅栏里,同一时刻只允许一个线程执行写的操作,不允许其他读与写。

4.timer

GCD定时器不是由CFRunLoopTimer实现的,不需要加入到runloopMode中,不受滑动等模式切换的影响,甚至切换到后台时依然能正常运行。

1
2
3
4
5
6
7
8
9
10
11
//1默认主线程
//timer = DispatchSource.makeTimerSource()
//2指定线程
timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.global())
timer?.schedule(deadline: DispatchTime.now(), repeating: .seconds(1), leeway: .milliseconds(10))
timer?.setEventHandler(handler: {
DispatchQueue.main.sync {
print("++")
}
})
timer?.resume()

5.延迟

1
2
3
4
5
6
7
8
let workItem = DispatchWorkItem {
print("++延迟执行1\(Thread.current)")
}
let queue = DispatchQueue.init(label: "after", attributes: .concurrent)
queue.asyncAfter(deadline: DispatchTime.now()+5, execute: workItem)
queue.asyncAfter(deadline: DispatchTime.now()+10) {
print("++延迟执行2\(Thread.current)")
}

6.信号量

使用信号量控制线程的并发数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var semaphore:DispatchSemaphore?

func semaphore {
semaphore = DispatchSemaphore(value: 2) // 最大并发数=2
for i in 0...10 {
let thread = Thread(target: self, selector: #selector(test), object: i as NSNumber)
thread.start()
}
}
@objc func test(index:NSNumber){
semaphore?.wait()
sleep(2)
print("++测试\(index.intValue)\(Date())")
semaphore?.signal()
}

使用信号量保证线程的同步与安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
semaphore = DispatchSemaphore(value: 1)
DispatchQueue.global().async {
self.semaphore?.wait()
print("++访问共享资源1")
sleep(5)
self.semaphore?.signal()
}
DispatchQueue.global().async {
self.semaphore?.wait()
print("++++访问共享资源2")
sleep(2)
self.semaphore?.signal()
}

相关参考:

#©


Swift中的GCD
https://davidlii.cn/2018/09/25/swift-gcd.html
作者
Davidli
发布于
2018年9月25日
许可协议