在swift中GCD采用链式调用,较OC而言使用方式更为简单,可读性更高。全文代码均默认在主线程中执行。
队列的获取与创建
//串行队列
let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
//并发队列
let concurrent = DispatchQueue(label: "serial",attributes: .concurrent)
//主队列
let mainQueue = DispatchQueue.main
//全局队列
let global = DispatchQueue.global()
GCD队列都遵循先进先出(FIFO)。所以往并发队列中添加同步任务,其执行顺序和任务的添加顺序相同。全局队列在功能上和并发队列是等价的,所以需要并发队列时,首选使用系统的全局队列。
这里要注意一点并发队列不能称为并行队列。请参考并发和并行的区别。
同步任务与异步任务
主队列+同步任务——死锁
同步任务会阻塞线程,在如下代码中需要优先执行print(2)
,等待其执行后才能继续往下,但是主队列为串行队列,需要等待当前任务执行完成后才能执行后加入队列的print(2)
任务,造成相互等待。
print(1)
DispatchQueue.main.sync {
print(2)
}
print(3)
主队列+异步任务——依次执行(不开启新线程)
以下代码中的print(2)
任务会添加到主队列的最后。又由于主线程+异步任务不会开启新线程,以下代码输出1 3 2,顺序固定不变。
print(1)
DispatchQueue.main.async {
print(2)
}
print(3)
串行队列+同步任务——依次执行
以下代码不管每个任务休眠时间多长,输出顺序始终为0...10
//串行队列
let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
for i in 0...10 {
serial.sync {
sleep(arc4random()%3)//休眠时间随机
print(i)
}
}
串行队列+异步任务——开启一个新线程依次执行
以下代码输出顺序始终为0...10,并且for循环中的Thread.current
的输出始终为同一个新线程
//串行队列
let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
print(Thread.current)//主线程
for i in 0...10 {
serial.async {
sleep(arc4random()%3)//休眠时间随机
print(i,Thread.current)//子线程
}
}
并发队列+同步任务——依次执行
以下代码输出顺序始终为0...10,且线程始终为主线程
for i in 0...10 {
DispatchQueue.global().sync {
sleep(arc4random()%3)//休眠时间随机
print(i,Thread.current)
}
}
并发队列+异步任务——开启多个线程并发执行
以下代码输出顺序随机,且线程信息不同。注意:这里可能不会输出11个不同的线程信息,经过代码测试发现当一个线程的任务执行完成后,如果队列中还有任务,此线程会继续被调度执行后续任务。 将任务数增多,结果更明显。
for i in 0...10 {
DispatchQueue.global().async {
sleep(arc4random()%3)//休眠时间随机
print(i,Thread.current)
}
}
并发队列——最大并发数
GCD并不能无限制的创建线程,如下代码其实最多创建64个子线程,意味着最大并发数为64。参考
for i in 0...1000 {
DispatchQueue.global().async {
print(i,Thread.current)
sleep(10000)
}
}
GCD 栅栏
在swift中栅栏不再是一个单独的方法。而是DispatchWorkItemFlags结构体中的一个属性。sync/async方法的其中一个参数类型即为DispatchWorkItemFlags,所以使用代码如下。这样的调用方式可以更好的理解栅栏,其实它就是一个分隔任务,将其添加到需要栅栏的队列中,以分隔添加前后的其他任务。以下代码栅栏前后均为并发执行。如果将添加栅栏修改为sync
则会阻塞当前线程。
for i in 0...10 {
DispatchQueue.global().async {
print(i)
}
}
DispatchQueue.global().async(flags: .barrier) {
print("this is barrier")
}
for i in 11...20 {
DispatchQueue.global().async {
print(i)
}
}
GCD group
队列组一般用来处理任务的依赖,比如需要等待多个网络请求返回后才能继续执行后续任务。
使用notify
添加结束任务
必须要等待group中的任务执行完成后才能执行,无法定义超时。
override func viewDidLoad() {
let group = DispatchGroup()
for i in 0...10 {
DispatchQueue.global().async(group: group) {
sleep(arc4random()%3)//休眠时间随机
print(i)
}
}
//queue参数表示以下任务添加到的队列
group.notify(queue: DispatchQueue.main) {
print("group 任务执行结束")
}
}
使用wait
进行等待——可定义超时
let group = DispatchGroup()
for i in 0...10 {
DispatchQueue.global().async(group: group) {
sleep(arc4random()%10)//休眠时间随机
print(i)
}
}
switch group.wait(timeout: DispatchTime.now()+5) {
case .success:
print("group 任务执行结束")
case .timedOut:
print("group 任务执行超时")
}
enter()
与leave()
enter()
是标示一个任务加入到队列,leave()
标识一个队列中的任务执行完成。
注意:enter()
与leave()
必须成对调用。
如果enter()
后未调用leave()
则不会触发notify,wait也只会timeout。
如果leave()
之前没有调用enter()
则会引起crash。
信号量 semaphore
GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数为0时等待,不可通过。计数为1或大于1时,计数减1且不等待,可通过。
let semaphore = DispatchSemaphore(value: 0)//创建一个信号量,并初始化信号总量
semaphore.signal()//发送一个信号让信号量加1
semaphore.wait()//可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
信号量处理线程同步
将异步执行任务转化为同步执行任务。如必须等待异步的网络请求返回后才能执行后续任务时。
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
sleep(arc4random()%5)//休眠时间随机
print("completed")
semaphore.signal()
}
switch semaphore.wait(timeout: DispatchTime.now()+10) {//信号量为0,调用wait后阻塞线程
case .success:
print("success")
case .timedOut:
print("timeout")
}
print("over")
信号量控制最大并发数
在Operation中可以通过maxConcurrentOperationCount
轻松实现控制最大并发数,GCD中需要借助信号量实现。以下代码就限制了最多两个任务并发执行。
let semaphore = DispatchSemaphore(value: 2)
for i in 0...10 {
semaphore.wait()//当信号量为0时,阻塞在此
DispatchQueue.global().async {
sleep(3)
print(i,Thread.current)
semaphore.signal()//信号量加1
}
print("=======================")
}
使用DispatchSemaphore加锁
非线程安全,即当一个变量可能同时被多个线程修改。以下代码如果不使用信号量输出是随机值。
let semaphore = DispatchSemaphore(value: 1)
var i = 0
for _ in 1...10 {
DispatchQueue.global().async {
semaphore.wait()//当信号量为0时,阻塞在此
for _ in 1...10 {
i += 1
}
print(i)
semaphore.signal()//信号量加1
}
}
延时任务
使用GCD执行延时任务指定的并不是任务的执行时间,而是加入队列的时间。所以执行时间可能不太精确。但是任务是通过闭包加入,相较performSelectorAfterDelay
可读性更好,也更安全。
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2) {
print("延时任务")
}
dispatch_once
在swift3以后dispatch_once
被废弃。swift中有更好的定义单例和一次性代码的方式。
DispatchWorkItem
与任务取消
DispatchWorkItem
其实就是用来代替OC中的dispatch_block_t
。如果任务是通过DispatchWorkItem
定义。在执行之前,可以执行取消操作。注意即使任务已经加入队列,只要还未执行就可以进行取消,但是无法判断任务在队列中的状态,所以一般会根据加入队列的时间确定是否可以取消。
let workItem = DispatchWorkItem {
print("延时任务")
}
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2, execute: workItem)
sleep(1)
workItem.cancel()
DispatchWorkItem
主动执行
let workItem = DispatchWorkItem {
print("workItem")
}
workItem.perform()
等待DispatchWorkItem
执行完成
let workItem = DispatchWorkItem {
sleep(3)
print("workItem")
}
DispatchQueue.global().async(execute: workItem)
switch workItem.wait(timeout: DispatchTime.now()+5) {
case .success:
print("success")
case .timedOut:
print("timeout")
}
DispatchWorkItem
执行完成通知
let workItem = DispatchWorkItem {
sleep(3)
print("workItem")
}
DispatchQueue.global().async(execute: workItem)
workItem.notify(queue: DispatchQueue.main) {
print("completed")
}
参考:
GCD最大线程数
iOS 多线程:『GCD』详尽总结