在swift中GCD采用鏈?zhǔn)秸{(diào)用,較OC而言使用方式更為簡(jiǎn)單,可讀性更高。全文代碼均默認(rèn)在主線程中執(zhí)行。
隊(duì)列的獲取與創(chuàng)建
//串行隊(duì)列
let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
//并發(fā)隊(duì)列
let concurrent = DispatchQueue(label: "serial",attributes: .concurrent)
//主隊(duì)列
let mainQueue = DispatchQueue.main
//全局隊(duì)列
let global = DispatchQueue.global()
GCD隊(duì)列都遵循先進(jìn)先出(FIFO)。所以往并發(fā)隊(duì)列中添加同步任務(wù),其執(zhí)行順序和任務(wù)的添加順序相同。全局隊(duì)列在功能上和并發(fā)隊(duì)列是等價(jià)的,所以需要并發(fā)隊(duì)列時(shí),首選使用系統(tǒng)的全局隊(duì)列。
這里要注意一點(diǎn)并發(fā)隊(duì)列不能稱為并行隊(duì)列。請(qǐng)參考并發(fā)和并行的區(qū)別。
同步任務(wù)與異步任務(wù)
主隊(duì)列+同步任務(wù)——死鎖
同步任務(wù)會(huì)阻塞線程,在如下代碼中需要優(yōu)先執(zhí)行print(2)
,等待其執(zhí)行后才能繼續(xù)往下,但是主隊(duì)列為串行隊(duì)列,需要等待當(dāng)前任務(wù)執(zhí)行完成后才能執(zhí)行后加入隊(duì)列的print(2)
任務(wù),造成相互等待。
print(1)
DispatchQueue.main.sync {
print(2)
}
print(3)
主隊(duì)列+異步任務(wù)——依次執(zhí)行(不開(kāi)啟新線程)
以下代碼中的print(2)
任務(wù)會(huì)添加到主隊(duì)列的最后。又由于主線程+異步任務(wù)不會(huì)開(kāi)啟新線程,以下代碼輸出1 3 2,順序固定不變。
print(1)
DispatchQueue.main.async {
print(2)
}
print(3)
串行隊(duì)列+同步任務(wù)——依次執(zhí)行
以下代碼不管每個(gè)任務(wù)休眠時(shí)間多長(zhǎng),輸出順序始終為0...10
//串行隊(duì)列
let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
for i in 0...10 {
serial.sync {
sleep(arc4random()%3)//休眠時(shí)間隨機(jī)
print(i)
}
}
串行隊(duì)列+異步任務(wù)——開(kāi)啟一個(gè)新線程依次執(zhí)行
以下代碼輸出順序始終為0...10,并且for循環(huán)中的Thread.current
的輸出始終為同一個(gè)新線程
//串行隊(duì)列
let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
print(Thread.current)//主線程
for i in 0...10 {
serial.async {
sleep(arc4random()%3)//休眠時(shí)間隨機(jī)
print(i,Thread.current)//子線程
}
}
并發(fā)隊(duì)列+同步任務(wù)——依次執(zhí)行
以下代碼輸出順序始終為0...10,且線程始終為主線程
for i in 0...10 {
DispatchQueue.global().sync {
sleep(arc4random()%3)//休眠時(shí)間隨機(jī)
print(i,Thread.current)
}
}
并發(fā)隊(duì)列+異步任務(wù)——開(kāi)啟多個(gè)線程并發(fā)執(zhí)行
以下代碼輸出順序隨機(jī),且線程信息不同。注意:這里可能不會(huì)輸出11個(gè)不同的線程信息,經(jīng)過(guò)代碼測(cè)試發(fā)現(xiàn)當(dāng)一個(gè)線程的任務(wù)執(zhí)行完成后,如果隊(duì)列中還有任務(wù),此線程會(huì)繼續(xù)被調(diào)度執(zhí)行后續(xù)任務(wù)。 將任務(wù)數(shù)增多,結(jié)果更明顯。
for i in 0...10 {
DispatchQueue.global().async {
sleep(arc4random()%3)//休眠時(shí)間隨機(jī)
print(i,Thread.current)
}
}
并發(fā)隊(duì)列——最大并發(fā)數(shù)
GCD并不能無(wú)限制的創(chuàng)建線程,如下代碼其實(shí)最多創(chuàng)建64個(gè)子線程,意味著最大并發(fā)數(shù)為64。參考
for i in 0...1000 {
DispatchQueue.global().async {
print(i,Thread.current)
sleep(10000)
}
}
GCD 柵欄
在swift中柵欄不再是一個(gè)單獨(dú)的方法。而是DispatchWorkItemFlags結(jié)構(gòu)體中的一個(gè)屬性。sync/async方法的其中一個(gè)參數(shù)類型即為DispatchWorkItemFlags,所以使用代碼如下。這樣的調(diào)用方式可以更好的理解柵欄,其實(shí)它就是一個(gè)分隔任務(wù),將其添加到需要柵欄的隊(duì)列中,以分隔添加前后的其他任務(wù)。以下代碼柵欄前后均為并發(fā)執(zhí)行。如果將添加?xùn)艡谛薷臑?code>sync則會(huì)阻塞當(dāng)前線程。
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
隊(duì)列組一般用來(lái)處理任務(wù)的依賴,比如需要等待多個(gè)網(wǎng)絡(luò)請(qǐng)求返回后才能繼續(xù)執(zhí)行后續(xù)任務(wù)。
使用notify
添加結(jié)束任務(wù)
必須要等待group中的任務(wù)執(zhí)行完成后才能執(zhí)行,無(wú)法定義超時(shí)。
override func viewDidLoad() {
let group = DispatchGroup()
for i in 0...10 {
DispatchQueue.global().async(group: group) {
sleep(arc4random()%3)//休眠時(shí)間隨機(jī)
print(i)
}
}
//queue參數(shù)表示以下任務(wù)添加到的隊(duì)列
group.notify(queue: DispatchQueue.main) {
print("group 任務(wù)執(zhí)行結(jié)束")
}
}
使用wait
進(jìn)行等待——可定義超時(shí)
let group = DispatchGroup()
for i in 0...10 {
DispatchQueue.global().async(group: group) {
sleep(arc4random()%10)//休眠時(shí)間隨機(jī)
print(i)
}
}
switch group.wait(timeout: DispatchTime.now()+5) {
case .success:
print("group 任務(wù)執(zhí)行結(jié)束")
case .timedOut:
print("group 任務(wù)執(zhí)行超時(shí)")
}
enter()
與leave()
enter()
是標(biāo)示一個(gè)任務(wù)加入到隊(duì)列,leave()
標(biāo)識(shí)一個(gè)隊(duì)列中的任務(wù)執(zhí)行完成。
注意:enter()
與leave()
必須成對(duì)調(diào)用。
如果enter()
后未調(diào)用leave()
則不會(huì)觸發(fā)notify,wait也只會(huì)timeout。
如果leave()
之前沒(méi)有調(diào)用enter()
則會(huì)引起crash。
信號(hào)量 semaphore
GCD 中的信號(hào)量是指 Dispatch Semaphore,是持有計(jì)數(shù)的信號(hào)。類似于過(guò)高速路收費(fèi)站的欄桿。可以通過(guò)時(shí),打開(kāi)欄桿,不可以通過(guò)時(shí),關(guān)閉欄桿。在 Dispatch Semaphore 中,使用計(jì)數(shù)來(lái)完成這個(gè)功能,計(jì)數(shù)為0時(shí)等待,不可通過(guò)。計(jì)數(shù)為1或大于1時(shí),計(jì)數(shù)減1且不等待,可通過(guò)。
let semaphore = DispatchSemaphore(value: 0)//創(chuàng)建一個(gè)信號(hào)量,并初始化信號(hào)總量
semaphore.signal()//發(fā)送一個(gè)信號(hào)讓信號(hào)量加1
semaphore.wait()//可以使總信號(hào)量減1,當(dāng)信號(hào)總量為0時(shí)就會(huì)一直等待(阻塞所在線程),否則就可以正常執(zhí)行。
信號(hào)量處理線程同步
將異步執(zhí)行任務(wù)轉(zhuǎn)化為同步執(zhí)行任務(wù)。如必須等待異步的網(wǎng)絡(luò)請(qǐng)求返回后才能執(zhí)行后續(xù)任務(wù)時(shí)。
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
sleep(arc4random()%5)//休眠時(shí)間隨機(jī)
print("completed")
semaphore.signal()
}
switch semaphore.wait(timeout: DispatchTime.now()+10) {//信號(hào)量為0,調(diào)用wait后阻塞線程
case .success:
print("success")
case .timedOut:
print("timeout")
}
print("over")
信號(hào)量控制最大并發(fā)數(shù)
在Operation中可以通過(guò)maxConcurrentOperationCount
輕松實(shí)現(xiàn)控制最大并發(fā)數(shù),GCD中需要借助信號(hào)量實(shí)現(xiàn)。以下代碼就限制了最多兩個(gè)任務(wù)并發(fā)執(zhí)行。
let semaphore = DispatchSemaphore(value: 2)
for i in 0...10 {
semaphore.wait()//當(dāng)信號(hào)量為0時(shí),阻塞在此
DispatchQueue.global().async {
sleep(3)
print(i,Thread.current)
semaphore.signal()//信號(hào)量加1
}
print("=======================")
}
使用DispatchSemaphore加鎖
非線程安全,即當(dāng)一個(gè)變量可能同時(shí)被多個(gè)線程修改。以下代碼如果不使用信號(hào)量輸出是隨機(jī)值。
let semaphore = DispatchSemaphore(value: 1)
var i = 0
for _ in 1...10 {
DispatchQueue.global().async {
semaphore.wait()//當(dāng)信號(hào)量為0時(shí),阻塞在此
for _ in 1...10 {
i += 1
}
print(i)
semaphore.signal()//信號(hào)量加1
}
}
延時(shí)任務(wù)
使用GCD執(zhí)行延時(shí)任務(wù)指定的并不是任務(wù)的執(zhí)行時(shí)間,而是加入隊(duì)列的時(shí)間。所以執(zhí)行時(shí)間可能不太精確。但是任務(wù)是通過(guò)閉包加入,相較performSelectorAfterDelay
可讀性更好,也更安全。
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2) {
print("延時(shí)任務(wù)")
}
dispatch_once
在swift3以后dispatch_once
被廢棄。swift中有更好的定義單例和一次性代碼的方式。
DispatchWorkItem
與任務(wù)取消
DispatchWorkItem
其實(shí)就是用來(lái)代替OC中的dispatch_block_t
。如果任務(wù)是通過(guò)DispatchWorkItem
定義。在執(zhí)行之前,可以執(zhí)行取消操作。注意即使任務(wù)已經(jīng)加入隊(duì)列,只要還未執(zhí)行就可以進(jìn)行取消,但是無(wú)法判斷任務(wù)在隊(duì)列中的狀態(tài),所以一般會(huì)根據(jù)加入隊(duì)列的時(shí)間確定是否可以取消。
let workItem = DispatchWorkItem {
print("延時(shí)任務(wù)")
}
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2, execute: workItem)
sleep(1)
workItem.cancel()
DispatchWorkItem
主動(dòng)執(zhí)行
let workItem = DispatchWorkItem {
print("workItem")
}
workItem.perform()
等待DispatchWorkItem
執(zhí)行完成
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
執(zhí)行完成通知
let workItem = DispatchWorkItem {
sleep(3)
print("workItem")
}
DispatchQueue.global().async(execute: workItem)
workItem.notify(queue: DispatchQueue.main) {
print("completed")
}
參考: