Swift GCD

一、DispatchQueue

DispatchQueue 分為串行和并發(fā),它的完整初始化方法為:

init(label: String, qos: DispatchQoS = default, attributes: DispatchQueue.Attributes = default, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = default, target: DispatchQueue? = default)

可見,這些參數(shù)中,除了label,其它都有默認值(label表示該隊列的標(biāo)簽,建議傳值為反向域名字符串,如:com.onevcat.Kingfisher.Animator.preloadQueue)。

當(dāng)除label外的參數(shù)都使用默認值時,初始化方法返回的便是串行隊列。如果需要返回并發(fā)隊列,參數(shù)attributes傳值為.concurrent即可。DispatchQueue.Attributes 是一個結(jié)構(gòu)體類型,該結(jié)構(gòu)體提供了兩個靜態(tài)變量:concurrentinitiallyInactive(注意,沒有代表串行隊列的靜態(tài)變量)。如果attributes參數(shù)傳值為initiallyInactive, 任務(wù)不會自動執(zhí)行,而是需要開發(fā)者手動調(diào)用activate()觸發(fā)。但是代碼依然是串行進行的,如果想要手動觸發(fā)、并行執(zhí)行任務(wù),可以指定attributes參數(shù)接受一個數(shù)組: [.concurrent, .initiallyInactive]。

參數(shù)qos代表隊列執(zhí)行的優(yōu)先級,有六種優(yōu)先級可供選擇:

unspecified
background
default
utility
userInteractive
userInitiated

優(yōu)先級從高到低依次為userInteractive>userInitiated>utility>background, 而default與unspecified介于userInteractive與background之間,具體有系統(tǒng)決定。

DispatchQueue.AutoreleaseFrequency有三種屬性值.inherit、.workItem.never
.inherit:不確定,之前默認的行為也是現(xiàn)在的默認值
.workItem:為每個執(zhí)行的任務(wù)創(chuàng)建自動釋放池,項目完成時清理臨時對象
.never:GCD不為您管理自動釋放池

參數(shù)target 用于指定即將創(chuàng)建的隊列與隊列target優(yōu)先級相同。也可通過setTarget(queue: DispatchQueue?)函數(shù)指定與queue相同的優(yōu)先級。

除了開發(fā)者自己創(chuàng)建隊列,還可以通過DispatchQueue.main獲取主隊列(主隊列也屬于串行隊列)、DispatchQueue.global(qos: DispatchQoS.QoSClass) 獲取全局并發(fā)隊列。

創(chuàng)建好了隊列,通過sync { /*任務(wù)*/ }async { /*任務(wù)*/ } 將任務(wù)追加到隊列中。串行隊列或并發(fā)隊列與sync或async組合總結(jié):

串行隊列 + sync : 隊列中的任務(wù)在當(dāng)前線程中依次執(zhí)行,后面追加的任務(wù)會等到前面追加的任務(wù)執(zhí)行完了才開始執(zhí)行,不開新線程。當(dāng)前線程取任務(wù)執(zhí)行的隊列不能與該串行隊列相同,否則會發(fā)生線程死鎖。
串行隊列(非主隊列) + async : 隊列中的任務(wù)在新線程中依次執(zhí)行。
主隊列 + async : 將任務(wù)追加到主隊列,當(dāng)主隊列中的其他任務(wù)執(zhí)行完之后才會執(zhí)行,并且在在主線程中執(zhí)行。
并發(fā)隊列 + sync : 隊列中的任務(wù)在當(dāng)前線程中依次執(zhí)行。
并發(fā)隊列 + async : 隊列中的任務(wù)在新線程中并發(fā)執(zhí)行。

不管哪種組合,隊列中的任務(wù)出列的方式都是FIFO。

有時候希望追加到queue中的任務(wù)暫不執(zhí)行,等待某一時刻執(zhí)行,這時候可使用隊列的suspend()函數(shù)和resume()函數(shù)。suspend()函數(shù)使隊列的暫停計數(shù)加1,resume()函數(shù)使隊列的暫停計數(shù)減一。

需要注意:
1、suspend()和resume()需要成對出現(xiàn),否則會crash。
2、suspend()和resume()函數(shù)只對自己創(chuàng)建的隊列有效,對系統(tǒng)提供的全局隊列無效。
3、suspend()和 resume()對隊列中的還未執(zhí)行的任務(wù)有效,對于正在執(zhí)行的任務(wù)無效。

二、DispatchGroup

在追加到DispatchQueue中的多個處理全部結(jié)束后想執(zhí)行結(jié)束處理,這個時候就可用到DispatchGroup。示例如下:

let group = DispatchGroup()
let queue = DispatchQueue.global()
queue.async(group: group) {
     print("任務(wù)一")
}
queue.async(group: group) {
      print("任務(wù)二")
}
queue.async(group: group) {
     print("任務(wù)三")
}
group.notify(queue: DispatchQueue.main) {
     print("完成任務(wù)一、二、三")
}
queue.async {
     print("任務(wù)四")
}

運行結(jié)果:


其中,queue既可以是同一個隊列,也可以是不同的隊列,既可以是串行隊列,也可以是并發(fā)隊列。
另外,也可以使用group的 group.wait(timeout: DispatchTime)group.wait(wallTimeout: DispatchWallTime)函數(shù)。wait 函數(shù)的參數(shù)表示等待的時間,默認是 DispatchTime.distantFuture,表示永久等待。wait函數(shù)會阻塞當(dāng)前線程,即當(dāng)執(zhí)行的時間到了等待的時長,才會執(zhí)行后面的代碼。wait函數(shù)返回值是枚舉類型DispatchTimeoutResult,DispatchTimeoutResult有success、timeOut兩個枚舉值,分別表示在等待時長內(nèi),任務(wù)執(zhí)行完成和未完成。
我們還可以通過group的enter()函數(shù)和leave()函數(shù)顯式表明任務(wù)是否執(zhí)行完成。代碼如下:

let group = DispatchGroup()
let queue = DispatchQueue.global()
group.enter()
queue.async {
     print("任務(wù)一")
     group.leave()
}
group.enter()
queue.async {
     print("任務(wù)二")
     group.leave()
}
group.enter()
queue.async {
    print("任務(wù)三")
    group.leave()
}
group.notify(queue: DispatchQueue.main) {
    print("完成任務(wù)一、二、三")
}
queue.async {
    print("任務(wù)四")
}

運行結(jié)果:


enter()leave()必須配合使用,有幾次enter就要有幾次leave,否則group會一直存在。當(dāng)所有enter的block都leave后,會執(zhí)行dispatch_group_notify的block。

三、asyncAfter

該函數(shù)用于延時操作。代碼如下:

DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now()+3) {
    print("執(zhí)行任務(wù)")
} 

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) {
    print("執(zhí)行任務(wù)")
}

注意, asyncAfter函數(shù)并不是在指定時間后執(zhí)行處理,而是在指定時間后將任務(wù)追加到隊列中。

asyncAfter函數(shù)的第一個參數(shù)可以是DispatchTime類型的值,也可以是DispatchWallTime類型的值。

DispatchTime 表示相對時間(相對設(shè)備啟動的時間,當(dāng)設(shè)備休眠時,計時也會暫停),精度為納秒級。DispatchTime.now() 獲取當(dāng)前相對時間,DispatchTime.now()基于當(dāng)前時間三秒后的時間,表達式中的3也可以使用DispatchTimeInterval.seconds(3)替換,或者用其他的時間單位:毫秒級DispatchTimeInterval.milliseconds(Int) 、微秒級DispatchTimeInterval.milliseconds(Int)、納秒級DispatchTimeInterval.nanoseconds(Int)。
DispatchWallTime 表示絕對時間(系統(tǒng)時間,設(shè)備休眠計時不暫停),精度是微秒。DispatchWallTime的用法和DispatchTime差不多。

四、DispatchWorkItem

DispatchWorkItem可以將任務(wù)封裝成DispatchWorkItem對象。

let workItem = DispatchWorkItem.init {
      print("執(zhí)行任務(wù)")
}

可以調(diào)用workItem的perform()函數(shù)執(zhí)行任務(wù),也可以將workItem追加到DispatchQueue或DispatchGroup中。以上所有傳block的地方都可換成DispatchWorkItem對象。
DispatchQueue還可以使用notify函數(shù)觀察workItem中的任務(wù)執(zhí)行結(jié)束,以及通過cancel()函數(shù)取消任務(wù)。

另外,workItem也可以像DispatchGroup一樣調(diào)用wait()函數(shù)等待任務(wù)完成。需要注意的是,追加workItem的隊列或調(diào)用perform()所在的隊列不能與調(diào)用workItem.wait()的隊列是同一個隊列,否則會出現(xiàn)線程死鎖。

DispatchWorkItem的完整初始化方法:

init(qos: DispatchQoS, flags: DispatchWorkItemFlags, block: () -> Void)

DispatchQoS前面已經(jīng)說過,不再贅述。DispatchWorkItemFlags類型的變量有六種:

static let assignCurrentContext: DispatchWorkItemFlags
static let barrier: DispatchWorkItemFlags
static let detached: DispatchWorkItemFlags
static let enforceQoS: DispatchWorkItemFlags
static let inheritQoS: DispatchWorkItemFlags
static let noQoS: DispatchWorkItemFlags

為了高效地讀寫數(shù)據(jù)庫或文件,通常需要將讀寫處理追加到并發(fā)隊列
中異步執(zhí)行,為了使讀寫操作不會引發(fā)數(shù)據(jù)競爭的問題,寫入操作不能與其他的寫入操作以及包含讀取任務(wù)的操作并發(fā)處理,這時便可設(shè)置flag的值為.barrier。代碼如下:

let queue = DispatchQueue.init(label: "com.codansYC.queue", attributes: DispatchQueue.Attributes.concurrent)
queue.async {
     print("讀數(shù)據(jù)1")
}
queue.async {
     print("讀數(shù)據(jù)2")
}
let workItem = DispatchWorkItem.init(qos: DispatchQoS.default, flags: DispatchWorkItemFlags.barrier) {
     print("開始寫數(shù)據(jù)------寫數(shù)據(jù)完成")
}
queue.async(execute: workItem)
queue.async {
     print("讀數(shù)據(jù)3")
}
queue.async {
     print("讀數(shù)據(jù)4")
}

運行結(jié)果:


注意,barrier只對自己創(chuàng)建的并發(fā)隊列才有效,對系統(tǒng)提供的全局并發(fā)隊列無效。

五、DispatchQueue.concurrentPerform

sync函數(shù)和Dispatch Group的關(guān)聯(lián)API。
DispatchQueue.concurrentPerform 會按指定次數(shù)異步執(zhí)行任務(wù),并且會等待指定次數(shù)的任務(wù)全部執(zhí)行完成,即會阻塞線程。建議在子線程中使用。

DispatchQueue.global().async {
     DispatchQueue.concurrentPerform(iterations: 5) { (i) in
         print("執(zhí)行任務(wù)\(i+1)")
     }
     print("任務(wù)執(zhí)行完成")
}

運行結(jié)果:

六、DispatchSemaphore

信號量。用于控制訪問資源的數(shù)量。比如系統(tǒng)有兩個資源可以被利用,同時有三個線程要訪問,只能允許兩個線程訪問,第三個會等待資源被釋放后再訪問。
信號量的初始化方法:DispatchSemaphore.init(value: Int),value表示允許訪問資源的線程數(shù)量,當(dāng)value為0時對訪問資源的線程沒有限制。
信號量配套使用wait()函數(shù)與signal()函數(shù)控制訪問資源。
wait函數(shù)會阻塞當(dāng)前線程直到信號量計數(shù)大于或等于1,當(dāng)信號量大于或等于1時,將信號量計數(shù)-1, 然后執(zhí)行后面的代碼。signal()函數(shù)會將信號量計數(shù)+1。

信號量是GCD同步的一種方式。前面介紹過的DispatchWorkItemFlags.barrier是對queue中的任務(wù)進行批量同步處理,sync函數(shù)是對queue中的任務(wù)單個同步處理,而DispatchSemaphore是對queue中的某個任務(wù)中的某部分(某段代碼)同步處理。此時將DispatchSemaphore.init(value: Int)中的參數(shù)value傳入1。代碼如下:

var arr = [Int]()
let semaphore = DispatchSemaphore.init(value: 1) // 創(chuàng)建信號量,控制同時訪問資源的線程數(shù)為1
for i in 0...100 {
    DispatchQueue.global().async {
                
        /*
        其他并發(fā)操作
        */
                
        semaphore.wait() // 如果信號量計數(shù)>=1,將信號量計數(shù)減1;如果信號量計數(shù)<1,阻塞線程直到信號量計數(shù)>=1
        arr.append(i)
        semaphore.signal() // 信號量計加1
                
        /*
        其他并發(fā)操作
        */
     }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容