由于GCD簡單易用,任務更簡單純粹,執(zhí)行效率高,本身性能高這些優(yōu)點,使得GCD在實際開發(fā)的使用和面試中出現(xiàn)的頻率非常高。掌握GCD及其多線程技術點并將其運用于開發(fā)中是開發(fā)一個良好易用App的基礎之一。本文主要整理了GCD的一些知識點和基礎用法。
1、GCD概覽
GCD全稱是Grand Central Dispatch,是Apple為開發(fā)者提供的一個多線程編程的方案,它是一個在線程池模式的基礎上執(zhí)行的并發(fā)任務,GCD充分利用硬件的多核性能,讓開發(fā)者編寫出更高效的代碼。
2、GCD的隊列和任務
2.1、隊列
- 主隊列(main Queue): 在主線程中執(zhí)行的任務,主要用于處理UI相關的任務
let main = DispatchQueue.main
- 串行隊列(serial Queue): 任務按照先后順序執(zhí)行,一般只分配一個線程,同一時刻只會執(zhí)行一個任務,并嚴格按照任務順序執(zhí)行
let serialQueue = DispatchQueue(label: "com.tsn.demo.serialQueue")
例如串行隊列中有三個下載圖片任務,系統(tǒng)會根據這三個任務加入到隊列的順序執(zhí)行,下載完成第一張圖片后才會下載第二張圖片,第二張圖片下載完成后才會下載第三張圖片。

- 并行隊列(concurrent Queue): 多個任務同時執(zhí)行,完成的順序不一定
let concurrentQueue = DispatchQueue(label: "com.tsn.demo.concurrentQueue", attributes: .concurrent)
創(chuàng)建隊列時設置attributes屬性為concurrent就是并行隊列。GCD會根據當前系統(tǒng)的狀況至少為并行隊列分配一個線程,且線程允許被任何隊列的任務阻塞。
如果是用并行隊列來下載圖片,GCD會根據當前系統(tǒng)狀況開啟多個線程下載圖片,下載圖片任務之間互不影響,完成的時間也不一定。

- 全局并發(fā)隊列 (global Queue)
let globalQueue = DispatchQueue.global()
let globalQueue = DispatchQueue.global(qos: .background)
全局并發(fā)隊列系統(tǒng)提供了6個不同的優(yōu)先級別,分別是background、utility、default、userInitiated、userInteractive、unspecified。創(chuàng)建隊列時,其中qos參數(shù)表示的是隊列的優(yōu)先級別,可以使用默認優(yōu)先級,也可以單獨指定。
2.2、任務
GCD的任務一般我們認為是程序執(zhí)行時做的事情,如一個API調用,一個方法、函數(shù)等。一般根絕
- 同步任務
queue.sync {
}
當前任務在執(zhí)行完并返回結果后才能執(zhí)行下一步,如圖所示,今天公司給我安排了一個開發(fā)任務A,當我正在開發(fā)時又出現(xiàn)了一個很緊急任務B此時我只能暫停做到一半的任務A來執(zhí)行任務B,當B任務完成后才能繼續(xù)開發(fā)任務A,這種執(zhí)行方式為同步。

- 異步任務
queue.async {
}
異步任務提交后不會阻塞當前線程,會由隊列安排另一個線程執(zhí)行。
如圖所示,今天給我安排了任務A,同時給另一個同事安排了任務B,我們兩開發(fā)的是同一個App此處可以看作是一個隊列,無論是誰先完成任務告知對方,然后完成代碼merge即可。這種執(zhí)行方式可以看作是異步。

- 柵欄任務
柵欄任務會對隊列中的任務進行阻隔,先把隊列中已有的任務全部執(zhí)行完然后再執(zhí)行柵欄任務。

- 迭代任務
如果說使用并行隊列是為了提高程序的執(zhí)行效率,那么迭代任務就是為了更高效的更全面的利用手機性能來執(zhí)行任務。
GCD使用concurrentPerform方法執(zhí)行迭代任務,類似于Objective-C的dispatch_apply方法。
2.3、隊列詳細屬性
前面已經給出一般我們創(chuàng)建隊列,系統(tǒng)提供的完成的創(chuàng)建方法:
let concurrentQueue = DispatchQueue(label: "com.tsn.demo.concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
2.3.1、label: label為隊列名稱,一般采用com+公司縮寫+工程名+線程名的命名方式。
2.3.2、QoS: 隊列在執(zhí)行上是有優(yōu)先級的,更高的優(yōu)先級可以享受更多的計算資源,從高到低的順序為:
- userInteractive
- userInitiated
- default
- utility
- background
2.3.3、Attributes標識隊列類型
- concurrent: 標識隊列為并行隊列
- initiallyInactive: 標識隊列運行中的任務需要手動觸發(fā),不設置此屬性,向隊列中添加任務自動運行,通過
queue.activate()方法觸發(fā)任務
let initiallyInactiveQueue = DispatchQueue(label: "com.tsn.demo.initiallyInactiveQueue", qos: .default, attributes: .initiallyInactive, autoreleaseFrequency: .inherit, target: nil)
initiallyInactiveQueue.async {
// do something
}
initiallyInactiveQueue.async {
// do something
}
initiallyInactiveQueue.async {
// do something
}
initiallyInactiveQueue.activate()
2.3.4、AutoreleaseFrequency: 表示autorelease pool的自動釋放頻率,autorelease pool用來處理任務對象的內存周期。系統(tǒng)提供了三個屬性:
- inherit: 繼承目標隊列的該屬性
- workItem: 跟隨每個任務的執(zhí)行周期自動創(chuàng)建和釋放
- never: 需要手動管理內存
2.3.5、Target: 一般創(chuàng)建的隊列不設置,如果沒有設置系統(tǒng)會自動設置,最終都指向系統(tǒng)主隊列或全局并發(fā)隊列。
let queue = DispatchQueue(label: "com.tsn.demo.queue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
即然最終都是指向主隊列或全局并發(fā)隊列,那為什么不直接將任務添加到主隊列或全局并發(fā)隊列中呢?通過自定義隊列可以將任務分組管理,這樣可以防止自定義隊列阻塞主隊列。
let queue = DispatchQueue(label: "com.tsn.demo.queue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: .main)
let queue = DispatchQueue(label: "com.tsn.demo.queue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: .global())
3、任務和隊列不同組合方式
3.1 同步 + 串行
print("3.1-----------\(Thread.current)-----------start")
serialQueue.sync {
sleep(1)
print("3.1.1-----------\(Thread.current)-----------")
}
serialQueue.sync {
sleep(1)
print("3.1.2-----------\(Thread.current)-----------")
}
serialQueue.sync {
sleep(1)
print("3.1.3-----------\(Thread.current)-----------")
}
serialQueue.sync {
sleep(1)
print("3.1.4-----------\(Thread.current)-----------")
}
print("3.1-----------\(Thread.current)-----------end")
3.1-----------<NSThread: 0x600000f58700>{number = 1, name = main}-----------start
3.1.1-----------<NSThread: 0x600000f58700>{number = 1, name = main}-----------
3.1.2-----------<NSThread: 0x600000f58700>{number = 1, name = main}-----------
3.1.3-----------<NSThread: 0x600000f58700>{number = 1, name = main}-----------
3.1.4-----------<NSThread: 0x600000f58700>{number = 1, name = main}-----------
3.1-----------<NSThread: 0x600000f58700>{number = 1, name = main}-----------end
可以得出結論:
- 不開啟新線程,所有任務都是在主線程中執(zhí)行的
- 任務是按順序執(zhí)行的,每次只執(zhí)行一個任務
3.2 異步 + 串行
print("3.2-----------\(Thread.current)-----------start")
serialQueue.async {
sleep(1)
print("3.2.1-----------\(Thread.current)-----------")
}
serialQueue.async {
sleep(1)
print("3.2.2-----------\(Thread.current)-----------")
}
serialQueue.async {
sleep(1)
print("3.2.3-----------\(Thread.current)-----------")
}
serialQueue.async {
sleep(1)
print("3.2.4-----------\(Thread.current)-----------")
}
print("3.2-----------\(Thread.current)-----------end")
打印結果為:
3.2-----------<NSThread: 0x600001974840>{number = 1, name = main}-----------start
3.2-----------<NSThread: 0x600001974840>{number = 1, name = main}-----------end
3.2.1-----------<NSThread: 0x6000019748c0>{number = 11, name = (null)}-----------
3.2.2-----------<NSThread: 0x6000019748c0>{number = 11, name = (null)}-----------
3.2.3-----------<NSThread: 0x6000019748c0>{number = 11, name = (null)}-----------
3.2.4-----------<NSThread: 0x6000019748c0>{number = 11, name = (null)}-----------
- 開啟新線程,注意此處只開啟了一條新線程
- 所有的任務是在
start和end之后執(zhí)行的,異步任務不做等待會直接執(zhí)行后面的任務 - 所有的任務是按順序執(zhí)行的
3.3 同步 + 并行
print("3.3-----------\(Thread.current)-----------start")
concurrentQueue.sync {
sleep(1)
print("3.3.1-----------\(Thread.current)-----------")
}
concurrentQueue.sync {
sleep(1)
print("3.3.2-----------\(Thread.current)-----------")
}
concurrentQueue.sync {
sleep(1)
print("3.3.3-----------\(Thread.current)-----------")
}
concurrentQueue.sync {
sleep(1)
print("3.3.4-----------\(Thread.current)-----------")
}
print("3.3-----------\(Thread.current)-----------end")
打印結果:
3.3-----------<NSThread: 0x600003914840>{number = 1, name = main}-----------start
3.3.1-----------<NSThread: 0x600003914840>{number = 1, name = main}-----------
3.3.2-----------<NSThread: 0x600003914840>{number = 1, name = main}-----------
3.3.3-----------<NSThread: 0x600003914840>{number = 1, name = main}-----------
3.3.4-----------<NSThread: 0x600003914840>{number = 1, name = main}-----------
3.3-----------<NSThread: 0x600003914840>{number = 1, name = main}-----------end
- 所有的任務都是在主線程中執(zhí)行的,沒有開啟新線程
- 所有的任務是在
start和end之間執(zhí)行的,同步任務需要等待隊列的任務執(zhí)行結果 - 任務是按照先后順序執(zhí)行的,雖然并發(fā)隊列可以開啟多個線程并且可以同時執(zhí)行多個任務,但是本身不能創(chuàng)建新線程。
3.4 異步 + 并行
print("3.4-----------\(Thread.current)-----------start")
concurrentQueue.async {
sleep(1)
print("3.4.1-----------\(Thread.current)-----------")
}
concurrentQueue.async {
sleep(1)
print("3.4.2-----------\(Thread.current)-----------")
}
concurrentQueue.async {
sleep(1)
print("3.4.3-----------\(Thread.current)-----------")
}
concurrentQueue.async {
sleep(1)
print("3.4.4-----------\(Thread.current)-----------")
}
print("3.4-----------\(Thread.current)-----------end")
打印結果
3.4-----------<NSThread: 0x6000035407c0>{number = 1, name = main}-----------start
3.4-----------<NSThread: 0x6000035407c0>{number = 1, name = main}-----------end
3.4.3-----------<NSThread: 0x600003550340>{number = 4, name = (null)}-----------
3.4.4-----------<NSThread: 0x6000035450c0>{number = 5, name = (null)}-----------
3.4.1-----------<NSThread: 0x600003545340>{number = 6, name = (null)}-----------
3.4.2-----------<NSThread: 0x600003544000>{number = 7, name = (null)}-----------
- 所有的任務是在
start和end之后執(zhí)行的,異步任務不做等待會直接執(zhí)行后面的任務 - 每個任務都開啟了一個新線程來執(zhí)行,異步執(zhí)行可以開啟新線程,并發(fā)隊列可以同時執(zhí)行多個任務
3.5 同步+主隊列
3.5.1 在主線程中調用 同步 + 主隊列
let mainQueue = DispatchQueue.main
mainQueue.sync {
sleep(1)
print("1-----------\(Thread.current)-----------")
}
mainQueue.sync {
sleep(1)
print("2-----------\(Thread.current)-----------")
}
mainQueue.sync {
sleep(1)
print("3-----------\(Thread.current)-----------")
}
運行代碼,發(fā)現(xiàn)沒有打印任何東西,原因是是我把任務放在了主線程隊列中,由于同步任務會等待當前隊列中的任務完成后才會繼續(xù)執(zhí)行的特性,此時主隊列和我添加的任務會互相等待,所以任務不會執(zhí)行也沒有任何打印。
3.5.1 在其他線程中調用 同步 + 主隊列
let queue = DispatchQueue(label: "com.tsn.test.queue", attributes: .concurrent)
queue.async {
print("0-----------\(Thread.current)-----------start")
let mainQueue = DispatchQueue.main
mainQueue.sync {
sleep(1)
print("1-----------\(Thread.current)-----------")
}
mainQueue.sync {
sleep(1)
print("2-----------\(Thread.current)-----------")
}
mainQueue.sync {
sleep(1)
print("3-----------\(Thread.current)-----------")
}
print("4-----------\(Thread.current)-----------end")
}
打印結果:
0-----------<NSThread: 0x600001f08580>{number = 1, name = main}-----------start
1-----------<NSThread: 0x600001f08580>{number = 1, name = main}-----------
2-----------<NSThread: 0x600001f08580>{number = 1, name = main}-----------
3-----------<NSThread: 0x600001f08580>{number = 1, name = main}-----------
4-----------<NSThread: 0x600001f08580>{number = 1, name = main}-----------end
- 雖然我新建了一個線程,但任務依舊是在主線程中執(zhí)行的,沒有開啟新線程
- 所有任務都是在
start和end之間執(zhí)行的 - 主隊列是串行隊列,所以任務是按順序執(zhí)行的
3.6 異步+主隊列
print("3.6-----------\(Thread.current)-----------start")
let mainQueue = DispatchQueue.main
mainQueue.async {
sleep(1)
print("3.6.1-----------\(Thread.current)-----------")
}
mainQueue.async {
sleep(1)
print("3.6.2-----------\(Thread.current)-----------")
}
mainQueue.async {
sleep(1)
print("3.6.3-----------\(Thread.current)-----------")
}
print("3.6-----------\(Thread.current)-----------end")
打印結果:
3.6-----------<NSThread: 0x600002590240>{number = 1, name = main}-----------start
3.6-----------<NSThread: 0x600002590240>{number = 1, name = main}-----------end
3.6.1-----------<NSThread: 0x600002590240>{number = 1, name = main}-----------
3.6.2-----------<NSThread: 0x600002590240>{number = 1, name = main}-----------
3.6.3-----------<NSThread: 0x600002590240>{number = 1, name = main}-----------
- 所有的任務都是在主線程中執(zhí)行的,沒有開啟新線程
- 所有任務都是在
start和end之后執(zhí)行的 - 任務是按順序執(zhí)行的
對于上面的各個組合做一個整理,如下表所示:
| 區(qū)別 | 串行隊列 | 并行隊列 | 主隊列 |
|---|---|---|---|
| 同步(sync) | 不開啟新線程,串行執(zhí)行 | 沒有開啟新線程,串行執(zhí)行 | 1、在主線程中調用時會造成死鎖;2、在其他線程中調用時:不開啟新線程在主線程中執(zhí)行,串行執(zhí)行 |
| 異步(async) | 開啟一條新線程,串行執(zhí)行 | 開啟多個新線程,并行執(zhí)行 | 沒有開啟新線程,串行執(zhí)行 |
4、GCD隊列和任務詳解
前面簡單介紹了GCD的任務和隊列,此處結合例子和示例代碼來進一步說明GCD實際開發(fā)中的使用。
(1)、串行隊列和并行隊列: 拿平時下載電影來舉例說明,默認情況下視頻資源的下載按照從上到下的順序來下載(這就是串行隊列)。但此時我的的目標是下載所有的電影,我并不關心電影下載的順序,所以此時可以讓多個下載任務同時執(zhí)行。(這就是并行隊列)。
// 串行隊列
let serialQueue = DispatchQueue(label: "com.tsn.demo.serialQueue")
// 并行隊列
let concurrentQueue = DispatchQueue(label: "com.tsn.demo.concurrentQueue", attributes: .concurrent)
(2)、對于多個下載任務而言,可以多個下載任務同時下載各個下載任務之間互不影響(這是異步執(zhí)行)。但是在播放視頻時只能等前一個視頻播放完了才能播放第二個視頻(這是同步執(zhí)行)。
// 同步執(zhí)行
queue.sync {
// 播放視頻一
}
queue.sync {
// 播放視頻二
}
queue.sync {
// 播放視頻三
}
// 異步執(zhí)行
queue.async {
// 下載視頻一
}
queue.async {
// 下載視頻二
}
queue.async {
// 下載視頻三
}
(3)、如果視頻還在下載中就點了播放鍵,此時要保證視頻、音頻、彈幕這三個捆綁到一起下載(這是任務組)。只有這三個下載任務全部完了在播放視頻(柵欄任務)。
柵欄任務示例代碼:
let barrierQueue = DispatchQueue(label: "com.tsn.demo.barrierQueue", attributes: .concurrent)
let barrierTask = DispatchWorkItem(qos: .default, flags: .barrier) {
// 點擊開始播放視頻
print("-----------開始播放------------")
}
barrierQueue.async {
// 下載視頻
print("-----------下載視頻------------")
}
barrierQueue.async {
// 下載音頻
print("-----------下載音頻------------")
}
barrierQueue.async {
// 下載彈幕
print("-----------下載彈幕------------")
}
// 柵欄任務
barrierQueue.async(execute: barrierTask)
運行代碼,打印的結果為:
-----------下載視頻------------
-----------下載音頻------------
-----------下載彈幕------------
-----------開始播放------------
在實際的開發(fā)中,視頻的播放、下載、緩存要比上面的舉例要復雜的多。如在傳輸過程中可能會出現(xiàn)丟包、掉幀等情況。
(4)、如果在下載途中,因為其他操作如我點擊了暫停,或來了個電話(這是掛起隊列),過了一會又點擊繼續(xù)開始下載(喚醒隊列),同時這些操作還要用到下載任務的暫停和繼續(xù)。
GCD可以把尚未執(zhí)行的任務掛起,但是不影響正在執(zhí)行和已執(zhí)行的任務,掛起的任務后續(xù)可手動在其喚醒。
suspend()方法掛起任務,resume()方法喚醒任務,此處要注意的是調用喚醒的次數(shù)必須等于掛起的次數(shù),否則就會出現(xiàn)不可預測的錯誤。
下面的代碼簡單
class SuspendAndResum {
let queue = DispatchQueue(label: "com.tsn.demo.concurrentQueue", attributes: .concurrent)
// 記錄隊列掛起的次數(shù)
var index = 0
init() {
// 模擬任務掛起
configQueue()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
// 模擬任務喚醒
self.testResume()
// 喚醒任務后繼續(xù)下載 下載完成后播放視頻
self.goOnQueue()
}
}
func configQueue() {
queue.async {
print("-----------模擬下載視頻-----------")
}
queue.async {
print("-----------模擬下載音頻-----------")
}
queue.async {
print("-----------模擬下載彈幕-----------")
}
// 通過柵欄掛起任務
queue.async(execute: DispatchWorkItem(flags: .barrier) {
self.testSuspend()
})
}
func goOnQueue() {
queue.async {
print("-----------繼續(xù)下載視頻-----------")
}
queue.async {
print("-----------繼續(xù)下載音頻-----------")
}
queue.async {
print("-----------繼續(xù)下載彈幕-----------")
}
let barrierTask = DispatchWorkItem(qos: .default, flags: .barrier) {
print("-----------下載完成,開始播放------------")
}
queue.async(execute: barrierTask)
}
// 掛起
func testSuspend() {
index = index + 1
queue.suspend()
print("-----------任務掛起-----------")
}
// 喚醒
func testResume() {
if index == 1 {
queue.resume()
index == 0
print("-----------任務喚醒-----------")
} else if index < 1 {
print("-----------喚醒次數(shù)超過掛起次數(shù)-----------")
} else {
queue.resume()
index = index - 1
print("-----------還需要\(index)才可以喚醒-----------")
}
}
}
let test = SuspendAndResum()
運行代碼,最終打印的結果為:
-----------模擬下載視頻-----------
-----------模擬下載音頻-----------
-----------模擬下載彈幕-----------
-----------任務掛起-----------
-----------任務喚醒-----------
-----------繼續(xù)下載視頻-----------
-----------繼續(xù)下載音頻-----------
-----------繼續(xù)下載彈幕-----------
-----------下載完成,開始播放------------
- (5)、一般一個隊列都可以設置最大并發(fā)數(shù),即使不設置GCD回根據當前系統(tǒng)的狀態(tài)自動設置并發(fā)數(shù)。拿下載視頻來說,我可以設置同時最大下載數(shù),這樣就可以控制同時進行的任務數(shù)。(信號量)
let queue = DispatchQueue(label: "com.tsn.demo.concurrentQueue", attributes: .concurrent)
// 設置最大并發(fā)數(shù)為5
let semap = DispatchSemaphore.init(value: 5)
// 信號量減1
semap.wait()
queue.async {
// 信號量加1
semap.signal()
}
// 信號量減1
semap.wait()
- (6)、我非常喜歡在睡前刷視頻,這樣我就有個需求,當我看完這個視頻,比如說20分鐘后就把視頻關了,此時就用到了延遲任務(asyncAfter)。
DispatchQueue.main.asyncAfter(deadline: .now() + 20 * 60) {
print("-----------20分鐘后關閉視頻------------")
}
- (7)、我要查找出2020年所有新歌歌名里含有
愛字的歌曲,由于要查找的數(shù)據源數(shù)據太多,采用遍歷的方式效率并不是很好,此時就可以用到迭代任務(concurrentPerform)。
// 迭代任務
let musicArray = Array<AnyObject?>(repeating: nil, count: 1008611)
DispatchQueue.concurrentPerform(iterations: 1008611) { (index) in
print("-----------執(zhí)行查找操作-----------")
}
5、任務組
任務組是將多個任務放到一個組里,DispatchGroup會等待組里面的任務都完成了之后通過notify()方法通知外部隊列任務已全部完成。同時采用enter()和leave()配對方法,標識任務加入或離開任務組。
此處同樣用下載視頻來舉例,將下載視頻、音頻、彈幕作為
// 創(chuàng)建任務組
let group = DispatchGroup()
// 下載視頻
let videoQueue = DispatchQueue(label: "com.tsn.demo.video", attributes: .concurrent)
videoQueue.async(group: group) {
print("-----------開始下載視頻-----------")
}
// 下載音頻
let audioQueue = DispatchQueue.init(label: "com.tsn.demo.audio", attributes: .concurrent)
audioQueue.async(group: group) {
print("-----------開始下載音頻-----------")
}
// 下載彈幕
let bulletScreenQueue = DispatchQueue.init(label: "com.tsn.demo.audio", attributes: .concurrent)
audioQueue.async(group: group) {
print("-----------開始下載彈幕-----------")
}
-
enter()和leave()方法
// 進入任務組
group.enter()
videoQueue.async(group: group) {
print("-----------開始下載視頻-----------")
// 退出任務組
group.leave()
}
-
notify()方法
// 任務組通知
group.notify(queue: .main) {
print("-----------全部下載完成-----------")
}
-
wait()方法
該方法會在所有任務完成后再執(zhí)行當前線程中的后續(xù)代碼,主要是起到阻塞線程的作用.同時也可以設置阻塞時間,如果所有任務都在指定時間內完成,否則等到時間結束后再恢復阻塞線程。
group.wait()
group.wait(timeout: .now() + 1)
group.wait(wallTimeout: .now() + 1)
6、DispatchSource
DispatchSource用來監(jiān)聽系統(tǒng)底層一些對象的活動,當這些事件發(fā)生時,會自動向隊列提交一個異步任務來處理這些事件。如我可以用DispatchSource監(jiān)聽電影是否成功下載到本地。
DispatchSource的常用方法有:
- 使用
makeFileSystemObjectSource、makeTimerSource創(chuàng)建source - 通過
setEventHandler方法設置監(jiān)聽 - 調用
resume()方法開始監(jiān)聽 - 調用
setCancelHandler方法取消監(jiān)聽
一般也常用于創(chuàng)建計時器,示例代碼,
var num = 5
let source: DispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue: .global())
source.schedule(deadline: .now(), repeating: 1)
// 設置監(jiān)聽
source.setEventHandler {
num = num - 1
if num < 0 {
source.cancel()
} else {
print("num-----------\(num)-----------")
}
}
source.resume()
打印結果
num-----------4-----------
num-----------3-----------
num-----------2-----------
num-----------1-----------
num-----------0-----------
更多使用方法可參考Apple Developer DispatchSource
7、線程安全
多個線程同時訪問同一個數(shù)據源時很容易引發(fā)數(shù)據錯亂和數(shù)據安全問題,這就是線程安全要解決的問題。
for index in 0..<123 {
queue.async {
array.remove(at: index)
print("-----------\(index)-----------")
}
}
for index in 0..<123 {
queue.async {
lock.lock()
array.remove(at: index)
print("-----------\(index)-----------")
lock.unlock()
}
}
如上面的代碼,雖然兩段代碼打印的結果是一樣的,但是第一段代碼是線程不安全的,第二段代碼是安全的。
1、線程死鎖
一般指多個線程互相等待而造成的線程循環(huán)等待。常見的有線程死鎖有:
- 主隊列同步
// 線程死鎖
DispatchQueue.main.sync {
print("-----------死鎖-----------")
}
- 串行 + 同步 + 嵌套同步自身隊列
// 串行隊列 + 同步 嵌套自身同步
let serialQueue = DispatchQueue.init(label: "com.tsn.LockTestClass")
serialQueue.sync {
serialQueue.async {
print("-----------死鎖-----------")
}
}
- 串行 + 異步 + 嵌套同步自身隊列
// 串行隊列 + 異步 嵌套自身同步
let serialQueue = DispatchQueue.init(label: "com.tsn.LockTestClass")
serialQueue.async {
serialQueue.async {
print("-----------死鎖-----------")
}
}
在這種情況下,Xcode會拋出crash。
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
得出結論:不要在串行或主隊列中嵌套執(zhí)行同步任務,才能有效避免線程死鎖。
2、線程安全的一些概念
- 臨界區(qū):就是一段代碼不能被并發(fā)執(zhí)行,也就是說兩個線程不能同時執(zhí)行同一段代碼。
- 競態(tài)條件:兩個或多個線程讀寫某些共享數(shù)據,而最后的結果取決于額線程運行的精確時序。
- 優(yōu)先級反轉:
如圖所示,有多個不同優(yōu)先級的線程,在第一個時間點,低優(yōu)先級的線程獲取到了鎖的資源,在第二個時間點高優(yōu)先級的線程去獲取這個加了鎖的資源,但是此時這個資源正被低優(yōu)先級的線程持有,此時高優(yōu)先級的線程就會被掛起并等待資源的釋放。此時我的中優(yōu)先級的線程不需要獲取鎖所以會比低優(yōu)先級優(yōu)先運行,低優(yōu)先級的線程只能等待中優(yōu)先級的線程執(zhí)行完之后才能去執(zhí)行,所以低優(yōu)先級線程也被掛起等待中優(yōu)先級線程執(zhí)行完成后在執(zhí)行。造成的結果就是中優(yōu)先級的線程會執(zhí)行的非常順利,而高優(yōu)先級和中優(yōu)先級線程卻不能按照預期執(zhí)行。這種情況就是優(yōu)先級反轉。

- 并發(fā)與并行
這兩個概念和手機內核有關,并發(fā)是指多個線程同時執(zhí)行。
并發(fā)是指通過內部的算法,把多個線程的執(zhí)行做切換,看起來像多個線程是同時執(zhí)行的。
3、各種不同的線程鎖
不再安全的 OSSpinLock這篇文中列舉了iOS中各種不同的鎖及其性能比較。
- 1、自旋鎖 (Spin Lock)
自旋鎖,任意時刻只有一個線程能夠獲得鎖,其他線程忙等待直到獲得鎖。SpinLock會一直檢測是否獲取某個鎖,雖然效率很好,但是非常消耗系統(tǒng)內存。所以應該慎重使用。
- 2、互斥鎖(Mutex Lock)
在Objective-C中可以用@synchronized來實現(xiàn)互斥鎖。其本質是調用objc_sync_enter和objc_sync_exit方法。因此在Swift中互斥鎖的實現(xiàn)方式是:
- (void)configMutex(id)lockData {
@synchronized(lockData)
}
func synchronized(lockData: AnyObject, ) {
objc_sync_enter(lockData)
objc_sync_exit(lockData)
}
參考貓神:LOCK
關于iOS中更多的線程鎖可參考: 掘金 深入理解 iOS 開發(fā)中的鎖
本文主要整理了GCD的基礎知識點和簡單使用,如果有錯誤或不對的地方,歡迎指出和補充。
相關鏈接: