在swift中GCD采用鏈?zhǔn)秸{(diào)用胎源,較OC而言使用方式更為簡單局扶,可讀性更高就谜。全文代碼均默認(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ì)列是等價的翻伺,所以需要并發(fā)隊(duì)列時,首選使用系統(tǒng)的全局隊(duì)列沮焕。
這里要注意一點(diǎn)并發(fā)隊(duì)列不能稱為并行隊(duì)列吨岭。請參考并發(fā)和并行的區(qū)別。
同步任務(wù)與異步任務(wù)
主隊(duì)列+同步任務(wù)——死鎖
同步任務(wù)會阻塞線程峦树,在如下代碼中需要優(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í)行(不開啟新線程)
以下代碼中的print(2)
任務(wù)會添加到主隊(duì)列的最后。又由于主線程+異步任務(wù)不會開啟新線程化戳,以下代碼輸出1 3 2单料,順序固定不變。
print(1)
DispatchQueue.main.async {
print(2)
}
print(3)
串行隊(duì)列+同步任務(wù)——依次執(zhí)行
以下代碼不管每個任務(wù)休眠時間多長点楼,輸出順序始終為0...10
//串行隊(duì)列
let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
for i in 0...10 {
serial.sync {
sleep(arc4random()%3)//休眠時間隨機(jī)
print(i)
}
}
串行隊(duì)列+異步任務(wù)——開啟一個新線程依次執(zhí)行
以下代碼輸出順序始終為0...10扫尖,并且for循環(huán)中的Thread.current
的輸出始終為同一個新線程
//串行隊(duì)列
let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
print(Thread.current)//主線程
for i in 0...10 {
serial.async {
sleep(arc4random()%3)//休眠時間隨機(jī)
print(i,Thread.current)//子線程
}
}
并發(fā)隊(duì)列+同步任務(wù)——依次執(zhí)行
以下代碼輸出順序始終為0...10,且線程始終為主線程
for i in 0...10 {
DispatchQueue.global().sync {
sleep(arc4random()%3)//休眠時間隨機(jī)
print(i,Thread.current)
}
}
并發(fā)隊(duì)列+異步任務(wù)——開啟多個線程并發(fā)執(zhí)行
以下代碼輸出順序隨機(jī),且線程信息不同掠廓。注意:這里可能不會輸出11個不同的線程信息换怖,經(jīng)過代碼測試發(fā)現(xiàn)當(dāng)一個線程的任務(wù)執(zhí)行完成后,如果隊(duì)列中還有任務(wù)蟀瞧,此線程會繼續(xù)被調(diào)度執(zhí)行后續(xù)任務(wù)沉颂。 將任務(wù)數(shù)增多,結(jié)果更明顯悦污。
for i in 0...10 {
DispatchQueue.global().async {
sleep(arc4random()%3)//休眠時間隨機(jī)
print(i,Thread.current)
}
}
并發(fā)隊(duì)列——最大并發(fā)數(shù)
GCD并不能無限制的創(chuàng)建線程铸屉,如下代碼其實(shí)最多創(chuàng)建64個子線程,意味著最大并發(fā)數(shù)為64切端。參考
for i in 0...1000 {
DispatchQueue.global().async {
print(i,Thread.current)
sleep(10000)
}
}
GCD 柵欄
在swift中柵欄不再是一個單獨(dú)的方法彻坛。而是DispatchWorkItemFlags結(jié)構(gòu)體中的一個屬性。sync/async方法的其中一個參數(shù)類型即為DispatchWorkItemFlags踏枣,所以使用代碼如下昌屉。這樣的調(diào)用方式可以更好的理解柵欄,其實(shí)它就是一個分隔任務(wù)茵瀑,將其添加到需要柵欄的隊(duì)列中间驮,以分隔添加前后的其他任務(wù)。以下代碼柵欄前后均為并發(fā)執(zhí)行瘾婿。如果將添加?xùn)艡谛薷臑?code>sync則會阻塞當(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ì)列組一般用來處理任務(wù)的依賴,比如需要等待多個網(wǎng)絡(luò)請求返回后才能繼續(xù)執(zhí)行后續(xù)任務(wù)偏陪。
使用notify
添加結(jié)束任務(wù)
必須要等待group中的任務(wù)執(zhí)行完成后才能執(zhí)行抢呆,無法定義超時。
override func viewDidLoad() {
let group = DispatchGroup()
for i in 0...10 {
DispatchQueue.global().async(group: group) {
sleep(arc4random()%3)//休眠時間隨機(jī)
print(i)
}
}
//queue參數(shù)表示以下任務(wù)添加到的隊(duì)列
group.notify(queue: DispatchQueue.main) {
print("group 任務(wù)執(zhí)行結(jié)束")
}
}
使用wait
進(jìn)行等待——可定義超時
let group = DispatchGroup()
for i in 0...10 {
DispatchQueue.global().async(group: group) {
sleep(arc4random()%10)//休眠時間隨機(jī)
print(i)
}
}
switch group.wait(timeout: DispatchTime.now()+5) {
case .success:
print("group 任務(wù)執(zhí)行結(jié)束")
case .timedOut:
print("group 任務(wù)執(zhí)行超時")
}
enter()
與leave()
enter()
是標(biāo)示一個任務(wù)加入到隊(duì)列笛谦,leave()
標(biāo)識一個隊(duì)列中的任務(wù)執(zhí)行完成抱虐。
注意:enter()
與leave()
必須成對調(diào)用。
如果enter()
后未調(diào)用leave()
則不會觸發(fā)notify饥脑,wait也只會timeout恳邀。
如果leave()
之前沒有調(diào)用enter()
則會引起crash懦冰。
信號量 semaphore
GCD 中的信號量是指 Dispatch Semaphore,是持有計(jì)數(shù)的信號谣沸。類似于過高速路收費(fèi)站的欄桿刷钢。可以通過時乳附,打開欄桿内地,不可以通過時,關(guān)閉欄桿赋除。在 Dispatch Semaphore 中阱缓,使用計(jì)數(shù)來完成這個功能,計(jì)數(shù)為0時等待举农,不可通過荆针。計(jì)數(shù)為1或大于1時,計(jì)數(shù)減1且不等待颁糟,可通過航背。
let semaphore = DispatchSemaphore(value: 0)//創(chuàng)建一個信號量,并初始化信號總量
semaphore.signal()//發(fā)送一個信號讓信號量加1
semaphore.wait()//可以使總信號量減1滚停,當(dāng)信號總量為0時就會一直等待(阻塞所在線程)沃粗,否則就可以正常執(zhí)行。
信號量處理線程同步
將異步執(zhí)行任務(wù)轉(zhuǎn)化為同步執(zhí)行任務(wù)键畴。如必須等待異步的網(wǎng)絡(luò)請求返回后才能執(zhí)行后續(xù)任務(wù)時。
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
sleep(arc4random()%5)//休眠時間隨機(jī)
print("completed")
semaphore.signal()
}
switch semaphore.wait(timeout: DispatchTime.now()+10) {//信號量為0突雪,調(diào)用wait后阻塞線程
case .success:
print("success")
case .timedOut:
print("timeout")
}
print("over")
信號量控制最大并發(fā)數(shù)
在Operation中可以通過maxConcurrentOperationCount
輕松實(shí)現(xiàn)控制最大并發(fā)數(shù)起惕,GCD中需要借助信號量實(shí)現(xiàn)。以下代碼就限制了最多兩個任務(wù)并發(fā)執(zhí)行咏删。
let semaphore = DispatchSemaphore(value: 2)
for i in 0...10 {
semaphore.wait()//當(dāng)信號量為0時惹想,阻塞在此
DispatchQueue.global().async {
sleep(3)
print(i,Thread.current)
semaphore.signal()//信號量加1
}
print("=======================")
}
使用DispatchSemaphore加鎖
非線程安全,即當(dāng)一個變量可能同時被多個線程修改督函。以下代碼如果不使用信號量輸出是隨機(jī)值嘀粱。
let semaphore = DispatchSemaphore(value: 1)
var i = 0
for _ in 1...10 {
DispatchQueue.global().async {
semaphore.wait()//當(dāng)信號量為0時,阻塞在此
for _ in 1...10 {
i += 1
}
print(i)
semaphore.signal()//信號量加1
}
}
延時任務(wù)
使用GCD執(zhí)行延時任務(wù)指定的并不是任務(wù)的執(zhí)行時間辰狡,而是加入隊(duì)列的時間锋叨。所以執(zhí)行時間可能不太精確。但是任務(wù)是通過閉包加入宛篇,相較performSelectorAfterDelay
可讀性更好娃磺,也更安全。
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2) {
print("延時任務(wù)")
}
dispatch_once
在swift3以后dispatch_once
被廢棄叫倍。swift中有更好的定義單例和一次性代碼的方式偷卧。
DispatchWorkItem
與任務(wù)取消
DispatchWorkItem
其實(shí)就是用來代替OC中的dispatch_block_t
豺瘤。如果任務(wù)是通過DispatchWorkItem
定義。在執(zhí)行之前听诸,可以執(zhí)行取消操作坐求。注意即使任務(wù)已經(jīng)加入隊(duì)列,只要還未執(zhí)行就可以進(jìn)行取消晌梨,但是無法判斷任務(wù)在隊(duì)列中的狀態(tài)瞻赶,所以一般會根據(jù)加入隊(duì)列的時間確定是否可以取消。
let workItem = DispatchWorkItem {
print("延時任務(wù)")
}
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2, execute: workItem)
sleep(1)
workItem.cancel()
DispatchWorkItem
主動執(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")
}
參考: