Dispatch ( 全稱 Grand Central Dispatch,簡稱 GCD ) 是一套由 Apple 編寫以提供讓代碼以多核併發的方式執行應用程序的框架。html
DispatchQueue
( 調度隊列 ) 就是被定義在 Dispatch 框架中,能夠用來執行跟多線程有關操做的類。swift
在使用它以前,咱們得先了解一下基本概念,我會先簡單介紹,後面再根據講解的內容逐步詳細介紹,目的是爲了方便讀者融入。多線程
PS:若是在閱讀時發現有任意錯誤,請指點我,感謝!併發
如圖。同步和異步的區別在於,線程會等待同步任務執行完成;線程不會等待異步任務執行完成,就會繼續執行其餘任務/操做。app
閱讀指南:框架
本文中出現的 "任務" 是指
sync {}
和async {}
中整個代碼塊的統稱,"操做" 則是在 "任務" 中執行的每一條指令 ( 代碼 ) ;由於主線程沒有 "任務" 之說,主線程上執行的每一條 ( 段 ) 代碼,都統稱爲 "操做"。異步
在 GCD 中,任務由**隊列 (串行或併發) **負責管理和決定其執行順序,在一條由系統自動分配的線程上執行。async
在串行 (Serial) 隊列中執行任務時,任務會按照固定順序執行,執行完一個任務後再繼續執行下一個任務 (這意味着串行隊列同時只能執行一個任務) ;在併發 (Concurrent) 隊列中執行任務時,任務能夠同時執行 ( 實際上是在以極短的時間內不斷的切換線程執行任務 ) 。oop
串行和併發隊列都以 先進先出 (FIFO) 的順序執行任務,任務的執行流程如圖:性能
sync
) 任務// 建立一個隊列(默認就是串行隊列,不須要額外指定參數)
let queue = DispatchQueue(label: "Serial.Queue")
print("thread: \(Thread.current)")
queue.sync {
(0..<5).forEach { print("rool-1 -> \($0): \(Thread.current)") }
}
queue.sync {
(0..<5).forEach { print("rool-1 -> \($0): \(Thread.current)") }
}
/** thread: <NSThread: 0x281951f40>{number = 1, name = main} rool-1 -> 0: <NSThread: 0x281951f40>{number = 1, name = main} rool-1 -> 1: <NSThread: 0x281951f40>{number = 1, name = main} rool-1 -> 2: <NSThread: 0x281951f40>{number = 1, name = main} rool-1 -> 3: <NSThread: 0x281951f40>{number = 1, name = main} rool-1 -> 4: <NSThread: 0x281951f40>{number = 1, name = main} rool-2 -> 0: <NSThread: 0x281951f40>{number = 1, name = main} rool-2 -> 1: <NSThread: 0x281951f40>{number = 1, name = main} rool-2 -> 2: <NSThread: 0x281951f40>{number = 1, name = main} rool-2 -> 3: <NSThread: 0x281951f40>{number = 1, name = main} rool-2 -> 4: <NSThread: 0x281951f40>{number = 1, name = main} */
複製代碼
沒什麼好解釋的,結果確定是按照正常的順序來,一個接着一個地執行。由於同步執行就是會一直等待,等到一個任務所有執行完成後,再繼續執行下一個任務。
有一點須要注意的是,主線程和在同步任務中 Thread,current
的打印結果相同,也就是說,隊列中的同步任務在執行時,系統給它們分配的線程是主線程,由於同步任務會讓線程等待它執行完,既然會等待,那就沒有再開闢線程的必要了。
當應用程序啓動時,就有一條線程被系統建立,與此同時這條線程也會馬上運行,該線程一般叫作程序的主線程。
同時系統也爲咱們提供一個名爲主隊列 ( DispatchQueue.main {}
) 的串行特殊隊列,默認咱們寫的代碼都處於主隊列中,主隊列中的全部任務都在主線程執行。
async
) 任務let queue = DispatchQueue(label: "serial.com")
print("thread: \(Thread.current)")
(0..<50).forEach {
print("main - \($0)")
// 讓線程休眠0.2s,目的是爲了模擬耗時操做,再也不贅述。
Thread.sleep(forTimeInterval: 0.2)
}
queue.async {
(0..<5).forEach {
print("rool-1 -> \($0): \(Thread.current)")
Thread.sleep(forTimeInterval: 0.2)
}
}
queue.async {
(0..<5).forEach {
print("rool-2 -> \($0): \(Thread.current)")
Thread.sleep(forTimeInterval: 0.2)
}
}
/** thread: <NSThread: 0x281251fc0>{number = 1, name = main} main - 0 main - 1 main - 2 ... 順序執行到 49 rool-1 -> 0: <NSThread: 0x281234100>{number = 3, name = (null)} rool-1 -> 1: <NSThread: 0x281234100>{number = 3, name = (null)} rool-1 -> 2: <NSThread: 0x281234100>{number = 3, name = (null)} rool-1 -> 3: <NSThread: 0x281234100>{number = 3, name = (null)} rool-1 -> 4: <NSThread: 0x281234100>{number = 3, name = (null)} rool-2 -> 0: <NSThread: 0x281234100>{number = 3, name = (null)} rool-2 -> 1: <NSThread: 0x281234100>{number = 3, name = (null)} rool-2 -> 2: <NSThread: 0x281234100>{number = 3, name = (null)} rool-2 -> 3: <NSThread: 0x281234100>{number = 3, name = (null)} rool-2 -> 4: <NSThread: 0x281234100>{number = 3, name = (null)} */
複製代碼
能夠看到,線程必定會等待它當前的操做 ( 包括讓線程休眠 ) 執行完後,再繼續執行 async
任務。此時任務一樣按順序執行,由於串行隊列只能執行完一個任務後再繼續執行下一個任務。
任務中 Thread.current
的打印結果都是 number = 3
,換句話說,串行隊列中的異步任務在執行時,系統給它們開闢的線程是其餘線程,而且只開闢一個,由於串行隊列同時只能執行一個任務,所以沒有開啓多條線程的必要。
這裏解釋一下 Thread.sleep
這個方法的做用:是讓當前線程暫停任何操做0.2s。
請注意我說的是當前線程,不要誤覺得是讓整個應用程序都中止了,不是這樣的。若是當前任務所在的線程中止了,是不會影響到別的線程正在執行任務的,這點要區分清楚。
PS:也就是說,在上面同步任務中,爲了測試而調用的 Thread.sleep
方法並無做用 ( 可是爲了測試和驗證,依然調用了 ) ,由於任務都在一條線程上,並按照固定順序執行。
async
) 任務 IIlet queue = DispatchQueue(label: "serial.com")
print("1: \(Thread.current)")
queue.async { print("2: \(Thread.current)") }
print("3: \(Thread.current)")
queue.async { print("4: \(Thread.current)") }
print("5: \(Thread.current)")
/** 1: <NSThread: 0x28347ed00>{number = 1, name = main} 3: <NSThread: 0x28347ed00>{number = 1, name = main} 2: <NSThread: 0x2834268c0>{number = 3, name = (null)} 5: <NSThread: 0x28347ed00>{number = 1, name = main} 4: <NSThread: 0x2834268c0>{number = 3, name = (null)} */
複製代碼
這時候打印的順序並不固定,但確定會先從 1
開始打印,打印的結果多是:12345, 12354, 13254, 13245, 13524, 13254...
,這是爲何?咱們先來了解一些概念後再來回顧。
首先要解釋一下同步和異步這兩個詞的概念,既然是同步或異步,也能解釋爲相同,或是不一樣,它須要一個做爲參照的對象,來知道它們相對於這個對象來講究竟是相同,仍是不一樣。
那在 GCD 中,它們的參照對象就是咱們的主線程 ( dispatchQueue.main
) 。也就是說若是是同步任務,那就在主線程執行;而若是是異步任務,那就在其餘線程執行。
這就解釋了,爲何串行隊列在執行異步任務時,還會開啓線程,所謂異步嘛,那就是不在主線程執行,區別是串行隊列只會開啓一條線程,而併發隊列會開啓多條線程。
而同步任務是,甭管它是什麼隊列和任務,只要執行的是同步任務,就在主線程執行 。
異步任務
異步任務說:「我要開始執行任務了,快給我分配線程讓我執行。」
應用程序說:「好!我另外開闢線程出來讓你執行,等等,請問你所處的隊列是?」
異步任務說:「串行隊列。」
應用程序說:「既然是串行隊列,而串行隊列中的全部任務都會按照固定順序執行,只能執行完一個任務後再繼續執行下一個任務 ( 這意味着串行隊列同時只能執行一個任務 ) ,那我就只給你分配一條線程吧!你隊列中的全部任務、包括你,都在這條線程上順序執行。」
異步任務說:「那若是我處在併發隊列中呢?」
應用程序說:「若是是在併發隊列中,那隊列中的全部任務能夠同時執行,我會給你分配多條線程,讓每一個任務能夠在不一樣的線程上同時執行。」
同步任務
同步任務說:「我要開始執行任務了,快給我分配線程讓我執行。」
應用程序說:「既然是同步任務那就至關於在主線程執行,那我就給你主線程來執行吧!」
同步任務說:「個人待遇太差了。」
任務只有兩種,同步任務和異步任務,不管同步任務是處在什麼隊列中,它都會讓當前正在執行的線程等待它執行完成,例如:
// 當前線程執行打印 main-1 的操做
print("main-1")
// 線程執行到這裏發現遇到一個 sync 任務,就會在此等待,
// 直到 sync 任務執行完成,纔會繼續執行其餘操做。
//
// 串行或併發隊列
queue.sync {
(0..<10).forEach {
print("sync \($0): \(Thread.current)")
Thread.sleep(forTimeInterval: 0.5)
}
}
// 等待!線程等待 sync 執行完後,再繼續執行打印 main-2 的操做。
print("main-2")
/** main-1 sync 0: <NSThread: 0x6000011968c0>{number = 1, name = main} sync 1: <NSThread: 0x6000011968c0>{number = 1, name = main} sync 2: <NSThread: 0x6000011968c0>{number = 1, name = main} sync 2 ...9 main-2 */
複製代碼
而若是是異步任務,無論它處在什麼隊列中,當前線程都不會等待它執行完成,例如:
// 當前線程執行打印 main-1 的操做
print("main-1")
// 線程執行到這裏發現遇到一個 async 任務,
// 那麼線程不會等待它執行完成,就會繼續執行其餘操做。
//
// 串行或併發隊列
queue.async {
(0..<20).forEach { print("async \($0)") }
}
// 開闢線程的時間大約是90微妙,加上循環的準備以及打印時間,
// 這裏給它200微妙,測試async任務中的線程和當前線程之間的執行順序。
Thread.sleep(forTimeInterval: 0.0002000)
// 不會等待!線程不會等待 async 執行完成就會執行打印 main-2 的操做
print("main-2")
複製代碼
打印的結果可能稍有不一樣,可是確定先從 main-1
開始打印。雖然 main-2
是執行在 async
後面的,async
也會先執行,可是因爲當前線程不等待它執行完成的機制,因此它在執行到某一刻時若是到了線程須要打印 main-2
的時間,就會執行打印 main-2
的操做。也有多是,main-2
先執行,而後等到了某一時刻再執行 async
中的任務 ( 開闢線程須要時間 ) 。
也就是說,這裏當前線程和 async
任務中的線程在執行時是不阻塞對方的 ( 互不等待 ) ,本次運行結果以下:
/** main-1 async 0 async 1 async 2 main-2 async 3 async 4 async 5 ... */
複製代碼
PS:我是怎麼知道開闢線程的時間大約是 90 微妙的?由於我看了線程成本中的描述。
這就能解釋以前示例中的執行順序了,再來回顧一下:
let queue = DispatchQueue(label: "serial.com")
print("1: \(Thread.current)")
queue.async { print("2-\(Thread.current)") }
print("3: \(Thread.current)")
queue.async { print("4: \(Thread.current)") }
print("5: \(Thread.current)")
複製代碼
雖然執行順序不固定,但仍是有必定的規律可循的,由於是串行隊列,因此在主線程中 1, 3, 5
必定按順序執行,而在 async
線程中 2, 4
也必定按順序執行。
首先,併發隊列不會出現死鎖的狀況;其次,在串行隊列中,只有 sync { sync {} }
和 async { sync {} }
會出現死鎖,內部的 sync closure 永遠不會被執行,而且程序會崩潰,例如:
queue.sync {
print("1")
queue.sync { print("2") }
print("3")
}
// Prints "1"
queue.async {
print("1")
queue.sync { print("2") }
print("3")
}
// Prints "1"
複製代碼
仔細觀察上面的代碼就會發現,只有內部套用 sync {}
的狀況下才會死鎖,那使用 sync
( 同步 ) 意味着什麼呢?這意味着,當前線程會等待同步任務執行完成。可問題是,這個 sync
任務是嵌套在另外一個任務裏面的 ( sync { sync {} }
) ,那這裏就有兩個任務了。
因爲串行隊列是執行完當前任務後,再繼續執行下一個任務。放到這裏就是,內部的 sync {}
想要執行的話,它必需要等待外部的 sync {}
執行完成,那外部的 sync {}
能不能執行完成呢?因爲這個內部任務是同步的,它會阻塞當前正在執行外部 sync {}
的線程,讓當前線程等待它 ( 內部 sync {}
) 執行完成,可問題是外部的 sync {}
完成不了的話,內部的 sync {}
也沒法執行,結果就是一直等待,誰都沒法繼續執行,形成死鎖。
既然線程會等待內部的同步任務執行完成,又限制串行隊列同時只能執行一個任務,那在外部的 sync {}
沒有執行完成以前,內部的 sync {}
永遠不能執行,而外部線程在等待內部 sync {}
執行完成的條件下,致使外部的 sync {}
也沒法執行完成。
總結:由於串行隊列同時只能執行一個任務,就意味着不管如何,線程只能先執行完當前任務後,再繼續執行下一個任務。而同步任務的特色是,會讓線程等待它執行完成。那問題就來了,我 ( 線程 ) 既不可能先去執行它,又要等待它,結果是致使外部任務永遠沒法執行完成,而內部的任務也永遠沒法開啓。
對於第二段代碼 async { sync {} }
的死鎖,原理是同樣的,不要被它外部的 async {}
給迷惑了,內部的 sync {}
一樣會阻塞它的線程執行,阻塞的結果就是外部的 async {}
沒法執行完成,內部的 sync {}
也永遠沒法開啓。
至於串行隊列另外兩種任務的嵌套結構 sync { async {} }
和 async { async }
,例如:
queue.sync {
print("task-1")
queue.async {
(0..<10).forEach {
print("task-2: \($0) \(Thread.current)")
Thread.sleep(forTimeInterval: 0.5)
}
}
print("task-1 - end")
}
/** 1 task-1 - end task-2: 0 <NSThread: 0x6000019c0d80>{number = 3, name = (null)} task-2: 1 <NSThread: 0x6000019c0d80>{number = 3, name = (null)} task-2: 2 ... 9 */
queue.sync {
print("task-1")
queue.async {
(0..<10).forEach {
print("task-2: \($0) \(Thread.current)")
Thread.sleep(forTimeInterval: 0.5)
}
}
print("task-1 - end")
}
/** 1 task-1 - end task-2: 0 <NSThread: 0x6000019c0d80>{number = 3, name = (null)} task-2: 1 <NSThread: 0x6000019c0d80>{number = 3, name = (null)} task-2: 2 ... 9 */
複製代碼
雖然已經再也不死鎖,但執行的順序稍有不一樣,能夠看到,程序是先把外部任務執行完後,再去執行內部任務。這是由於,內部的 async {}
已經再也不阻塞當前線程,又由於串行隊列只能先把當前任務執行完後,再去執行下一個任務,那天然而然就是先把外部任務執行完後,再接着去執行內部的 async {}
任務了。
前面說過,async
中的任務都會在其餘線程執行,那對於主隊列中的 async
呢?在項目中咱們常常調用的 DispatchQueue.main.asyncAfter(deadline:)
難道是在其餘線程執行嗎?其實不是的,若是是 DispatchQueue.main
本身的隊列,那麼即便是 async
,也會在主線程執行,因爲主隊列自己是串行隊列,也是同時只能執行一個任務,因此是,它會在處理完當前任務後,再去處理 async
中的任務,例如:
// 實際上至關於在 DispatchQueue.main.sync {} 中執行
print("1")
DispatchQueue.main.async {
(0..<10).forEach {
print("async\($0) \(Thread.current)")
Thread.sleep(forTimeInterval: 0.2)
}
}
print("3")
/** 1 3 async0 <NSThread: 0x6000007928c0>{number = 1, name = main} async1 <NSThread: 0x6000007928c0>{number = 1, name = main} async2 <NSThread: 0x6000007928c0>{number = 1, name = main} async3 ...9 */
複製代碼
雖然 async
不阻塞當前線程執行,可是因爲都在一個隊列上,DispatchQueue.main
只能先執行完當前任務後,再繼續執行下一個任務 ( async
) 。
而若是在主線程調用 DispatchQueue.main.sync {}
又會如何呢?答案是:會死鎖。其實緣由很簡單,由於整個主線程的代碼就至關於放在一個大的 DispatchQueue.main.sync {}
任務中,這時候若是再調用 DispatchQueue.main.sync {}
,結果確定是死鎖。
還有一點須要留意,必定要在主線程執行和有關 UI 的操做,若是是在其餘線程執行,例如:
queue.async { // 併發隊列
customView.backgroundColor = UIColor.blue
}
複製代碼
極可能就會接收到一個 Main Thread Checker: UI API called on a background thread: -[UIView setBackgroundColor:]
的崩潰報告,所以主線程也被稱爲 UI 線程。
sync
) 任務let queue = DispatchQueue(label: "serial.com", attributes: .concurrent)
queue.sync {
(0..<10).forEach {
print("task-1 \($0): \(Thread.current)")
Thread.sleep(forTimeInterval: 0.2)
}
}
print("main-1")
queue.sync {
(0..<10).forEach {
print("task-2 \($0): \(Thread.current)")
Thread.sleep(forTimeInterval: 0.2)
}
}
print("main-2")
/** task-1 0: <NSThread: 0x6000023968c0>{number = 1, name = main} task-1 1: <NSThread: 0x6000023968c0>{number = 1, name = main} task-1 2: <NSThread: 0x6000023968c0>{number = 1, name = main} task-1 3: <NSThread: 0x6000023968c0>{number = 1, name = main} task-1 4: <NSThread: 0x6000023968c0>{number = 1, name = main} main-1 task-2 0: <NSThread: 0x6000023968c0>{number = 1, name = main} task-2 1: <NSThread: 0x6000023968c0>{number = 1, name = main} task-2 2: <NSThread: 0x6000023968c0>{number = 1, name = main} task-2 3: <NSThread: 0x6000023968c0>{number = 1, name = main} task-2 4: <NSThread: 0x6000023968c0>{number = 1, name = main} main-2 */
複製代碼
使用併發隊列執行同步任務和在主線程執行操做並無區別,由於 sync
會緊緊的將當前線程固定住,讓線程等待它執行完成後才能繼續執行其餘操做。這裏也可以看到,main-1
和 main-2
分別等待 sync
執行結束後才能執行。
async
) 任務在線程將要執行到某個隊列的 async
時,隊列纔會開始併發執行任務,線程不可能跨越當前正在執行的操做去啓動任務。舉個例子:
// 指定爲建立併發隊列 (.concurrent)
let queue = DispatchQueue(label: "concurrent.com", attributes: .concurrent)
(0..<100).forEach {
print("main-\($0)")
Thread.sleep(forTimeInterval: 0.02)
}
queue.async { print("task-1", Thread.current) }
queue.async { print("task-2", Thread.current) }
queue.async { print("task-3", Thread.current) }
queue.async { print("task-4", Thread.current) }
queue.async { print("task-5", Thread.current) }
queue.async { print("task-6", Thread.current) }
print("main-end")
/** main-0 main-1 main-2 ...99 task-2 <NSThread: 0x282e387c0>{number = 3, name = (null)} task-4 <NSThread: 0x282e387c0>{number = 3, name = (null)} task-5 <NSThread: 0x282e387c0>{number = 3, name = (null)} task-3 <NSThread: 0x282e38800>{number = 5, name = (null)} task-6 <NSThread: 0x282e387c0>{number = 3, name = (null)} print("main-end") task-1 <NSThread: 0x282e04b40>{number = 4, name = (null)} */
複製代碼
由於主線程也是串行隊列,程序將按照順序執行,等到全部循環執行完成後,纔會執行 queue.async
,因爲是併發隊列,全部任務都會同時執行,執行順序並不固定,而最後的 main-end
可能安插在隊列中某個任務完成先後的地方。
由於在執行 main-end
以前,任務已經被隊列併發出去了。對於主線程來講,它完成打印 main-end
的時間是固定的,可是隊列中併發任務的執行完成的時間並不固定 ( 執行任務會消耗時間 ) 。這時主線程並不會等待 async
的全部任務執行結束就會繼續執行打印 main-end
的操做。
因此是,若是在執行 async
的某個時間內恰好到了主線程須要打印 main-end
的時間,就會執行打印 main-end
的操做,而 async
中尚未完成的任務將會繼續執行,如圖:
能夠看到,循環操做結束後,隊列纔開始併發執行任務,打印 main-end
的操做在 queue.async
以後執行,可是因爲隊列執行任務須要時間,因此 main-end
有可能在 queue.async
執行完成以前執行。
對於一條線程來講,它的全部操做絕對按照固定順序執行,不存在一條線程同時執行多個任務的狀況。而咱們的所謂併發,就是給每一個任務開闢一條線程出來執行,等到有某個線程執行完後,就會複用這條線程去執行其餘在隊列中尚未開始執行的任務。
一條線程只負責執行它當前任務中的全部操做,至於其餘線程被開啓後 ( 前提是不要開啓一樣的線程 ) ,它們就在各自的線程上分別獨立執行任務,互不影響。舉個例子:
假設你要跑100米,當跑到50米的時候,就會有5我的跟你一塊兒跑,跑到終點的時候,多是你跑得比他們都快,也有多是他們之中的任意人跑得比你快。
那你就能夠想象成那 "5我的" 就是併發中的任務 ( 同時執行) ,而 "你" 就是當前線程。
sync { sync {} }
那何時會開啓一樣的線程呢?也就是說,假設有一條線程 3 在執行,那麼在這條線程 3 尚未執行完成的時候,就又有一條線程爲 3 的任務開啓了。這對於 async
任務來講,幾乎不可能 ( 我說幾乎是由於我不肯定,按照個人猜想,應該不會出現這種狀況 ) ,也就是說,想要開啓一樣的一條線程執行異步任務,必需要等到前面的線程執行完後,再用這條線程去執行其餘任務。
可是對於 sync
任務來講,在 sync
還沒執行完的時候,我能夠在 sync {}
內部又開啓一個 sync {}
任務,由於 sync {}
註定在主線程執行 ( async
任務沒法指定在哪一條線程執行,而是由系統自動分配 ) ,這樣一來,就有了在一條線程尚未執行完的時候,就又有一條一樣的線程開啓執行任務了。在串行隊列中,咱們已經知道,這樣作會形成死鎖,那在併發隊列中又會如何呢?例如:
let queue = DispatchQueue(label: "concurrent.com", attributes: .concurrent)
queue.sync {
print("sync-start")
queue.sync {
(0..<5).forEach {
print("task \($0): \(Thread.current)")
Thread.sleep(forTimeInterval: 0.5)
}
}
print("sync-end")
}
/** sync-start task 0: <NSThread: 0x600003b828c0>{number = 1, name = main} task 1: <NSThread: 0x600003b828c0>{number = 1, name = main} task 2: <NSThread: 0x600003b828c0>{number = 1, name = main} task 3: <NSThread: 0x600003b828c0>{number = 1, name = main} task 4: <NSThread: 0x600003b828c0>{number = 1, name = main} sync-end */
複製代碼
咱們已經看到結果,任務按照順序執行,內部 sync
會阻塞外部 sync
咱們也會清楚,問題是在外部的 sync {}
尚未執行完的時候,爲何內部的 sync
能夠執行?
首先要了解最重要的一點,那就是,爲何在串行隊列中內部的 sync {}
沒法執行?最重要的緣由在於串行隊列同時只能執行一個任務,因此在它上一個任務 ( 外部 sync
) 尚未執行完成以前,它是不能執行下一個任務 ( 內部 sync
) 的。
而併發隊列就不一樣了,併發隊列能夠同時執行多個任務。也就是說,內部的 sync
已經不用等待外部 sync
執行完成就能夠執行了。可是因爲是同步任務,因此仍是會等待,等待內部 sync
執行完成後,外部的 sync
繼續執行。
請注意這裏的執行和上面所說的,不存在一條線程同時執行多個任務的狀況並不矛盾。由於在執行內部 sync
時,外部線程就中止操做了 ( 實際上是轉去執行內部 sync
了 ) ,若是是在執行內部 sync
的同時,外部的 sync
還在繼續執行操做,那才叫同時。
由於 sync
都在一個線程 ( 主線程 ) 上,因此當你指定任務爲 sync
時,主線程就知道接下來要去執行 sync
任務了,等執行完這個 sync
後再執行其餘操做。例如,你能夠把 sync
想象成是一個方法:
let queue = DispatchQueue(label: "concurrent.com", attributes: .concurrent)
queue.sync {
print("sync-start")
queueSync()
print("sync-end")
}
// 至關於以前的 queue.sync {}
func queueSync() {
(0..<5).forEach {
print("task \($0): \(Thread.current)")
Thread.sleep(forTimeInterval: 0.5)
}
}
複製代碼
對串行隊列來講,先進先出的意思很好理解,先進先出就是,先進去的必定先執行。當咱們要執行一些任務時,這些任務就被存儲在它的隊列中,當線程進入到任務代碼塊時,就必定會先把這個任務執行完,再將任務出列,等這個任務出列後,線程才能繼續去執行下一個任務。
那對於併發隊列也是同樣,當不一樣的線程同時進入到任務代碼塊時,就必定會先把這些任務執行完,再將這些任務出列,而後這些線程才能繼續去執行其餘任務。
let queue = DispatchQueue(label: "concurrent.com", attributes: .concurrent)
(0..<100).forEach { i in
queue.async { print("\(i) \(Thread.current)") }
}
複製代碼
會怎麼樣?答案是不會怎麼樣,只是會開啓不少線程來執行這些異步任務。前面說過,每個異步任務都是在不一樣的線程上執行的,那若是同時執行不少異步任務的話,像咱們這裏,同時開啓 100 個異步任務,難道就係統就開闢 100 個線程來分別執行嗎?也不是沒有可能,這取決於你的 CPU,若是在 App 運行時,系統所能承載的最大線程個數爲 10,那就會開闢這 10 條線程來重複執行任務,一次執行 10 個異步任務。
若是開闢的線程上限,那麼剩下的那些任務就暫時沒法執行,只能等到前面那些異步任務的線程執行完後,再去執行後面的異步任務。
總之一句話就是重複利用,先執行完的去執行尚未開始執行的,若是開闢的線程超出限制,那後面的任務就要等待前面的線程執行完才能執行。
可是若是開闢不少線程的話,會不會對咱們的應用程序有負的影響?答案是必定的,開闢一條線程就要消耗必定的內存空間和系統資源,若是同時存在不少線程的話,那自己留給應用程序的內存就少得可憐,應用程序在運行時就會很卡,因此並非線程開得越多越好,須要開發者本身平衡。
除了串行主隊列外,系統還爲咱們建立了一個全局的併發隊列 ( DispatchQueue.global()
) ,若是不想本身建立併發隊列,那就用系統的 ( 咱們通常也是用系統的 ) 。
DispatchQueue.global().async {
print("global async start \(Thread.current)")
DispatchQueue.global().sync {
(0..<5).forEach {
print("roop\($0) \(Thread.current)")
Thread.sleep(forTimeInterval: 0.2)
}
}
print("global async end \(Thread.current)")
}
/** global async start <NSThread: 0x600002085300>{number = 3, name = (null)} roop0 <NSThread: 0x600002085300>{number = 3, name = (null)} roop1 <NSThread: 0x600002085300>{number = 3, name = (null)} roop2 <NSThread: 0x600002085300>{number = 3, name = (null)} roop3 <NSThread: 0x600002085300>{number = 3, name = (null)} roop4 <NSThread: 0x600002085300>{number = 3, name = (null)} global async end <NSThread: 0x600002085300>{number = 3, name = (null)} */
複製代碼
和主隊列同樣,它的特殊之處在於,即便是用 sync
,任務也會在其餘線程執行,至於它在哪一條線程執行,我猜想是它必定會讓執行外部 async
的這條線程來執行,由於 sync
就是會讓線程暫停執行後續操做,等到 sync
執行完後再接着執行,也就是說,在這種狀況下,它只能順序執行,那彷佛只要一條線程就足夠了,沒有必要再開闢新線程來執行內部的 sync
。
另外,全局併發隊列只有一個,並非調用一次系統就建立一個,通過測試,它們是相等的:
let queue1 = DispatchQueue.global()
let queue2 = DispatchQueue.global()
if queue1 == queue2 { print("相等") }
// Prints "相等"
複製代碼
在前面的示例中,有關概念都是跟隨示例引伸出來的,講得不是那麼統一,在這裏就總結一下。
隊列
串行隊列 在串行隊列中執行任務時,任務按固定順序執行,只能執行完一個任務後,再繼續執行下一個任務 ( 這意味着串行隊列同時只能執行一個任務 ) 。
併發隊列
併發隊列能夠同時執行多個任務,任務並不必定按順序執行,先執行哪幾個任務由系統自動分配決定,等到有某個任務執行完後,就將這個任務出列,而後線程才能繼續去執行其餘任務。
任務
同步任務
無論是串行仍是異步隊列,只要是同步任務,就在主線程執行 ( DispatchQueue.global().sync
例外 ) 。
同步任務會阻塞當前線程,讓當前線程只能等待它執行完畢後才能執行。
在串行隊列中,任務嵌套了 sync {}
的話會致使死鎖。
異步任務
不管是串行仍是異步隊列,只要是異步任務,就在其餘線程執行 ( DispatchQueue.main.sync
例外 ) ,不一樣的是串行隊列在執行異步任務時,只會開闢一條線程,而併發隊列在執行異步任務時,能夠開闢多條線程。
異步任務不會阻塞當前線程,線程不用等待異步任務執行完成就能夠繼續執行其餘任務/操做。
異步任務不會產生死鎖。