這個浮誇標題是ChatGPT幫我想的😊 圖也是DALL幫我產的, AI萬歲!
GCD也是面試必問題啊!我答超爛的,只會用背景呼叫API+切Main Thread更新畫面,今天就來認真了解。
先來問ChatGPT什麼是GCD?
GCD,全名 Grand Central Dispatch,是 Apple 提供的一種用於管理並行執行任務的技術。它是一種低階的 C API,用於優化應用程序以便在多核硬體上更有效地並行運行。
GCD 提供了一種簡單的方式來管理並行任務,而不需要直接管理線程的生命週期。它允許你將任務(也稱為“塊”或“閉包”)提交到不同的隊列(Queue)中進行執行。這些隊列可以是串行的(Serial,一次只執行一個任務)或並行的(Concurrent,可同時執行多個任務)。
簡單來說就是接受任務的queue。比較輕量級,簡單快速。
把Code直接寫在block裡執行,不能等待也不能取消。
DispatchQueue.global().async {
// 一些比較花時間的操作
DispatchQueue.main.async {
// update UI
}
}
開發者自己創造的queue,最好是給他一個Label來識別(名稱寫法:com.xxx.xxxxx),因爲app裡會產生很多queue。預設是一次只執行一個任務,可以用attributes: .concurrent改成多個並行。
DispatchQueue(label: "com.custom.queue", attributes: .concurrent).async {
// 看你要做什麼
}
userInitiate > default > utility > background > unspecified
DispatchQueue.global(qos: .userInitiate).async {
// 看你要做什麼
DispatchQueue.main.async {
// update UI
}
}
GCD有一個需要注意的點,為了要完成所有的任務,他有可能會一直創造Thread,最後造成App耗盡所有的Thread跟資源。通常發生在下列兩種狀況:第一,某個在cocurrent queue上的任務擋住現在的Thread,GCD會在創造另一個Thread去執行其他的任務。第二,創造太多custom queue,每個queue也都會創造自己的Thread。建議沒有特殊需求用global queue即可。
這也是面試很愛考的。Deadlock是指雙方都互相在等對方,沒有一方先退出,造成死結。
官網上也簡單明暸一句話,在main queue上同步執行task會造成deadlock。
Attempting to synchronously execute a work item on the main queue results in deadlock.
在Xcode上試試看,viewDidLoad裡寫DispatchQueue.main.sync
會crash。
這邊crash的原因,我想是因為在main thread裡又用print同步組塞了main thread,app就直接crash了。
非同步裡再同步呼叫Main Thread編譯通過。
DispatchQueue.global().async {
print("1")
DispatchQueue.main.sync {
print("2")
}
print("3")
}
// console output:
// 1
// 2
// 3
第二種情境
測試Serial Queue的各種sync/async組合會不會出錯
Case 3 原理上會造成Deadlock,在async區塊內,有一個sync區塊被分派到同一個Serial Queue裡。裡面的sync block在等外面的async執行完,外面的async無法完成因為他也在等裡面的sync執行完,不過文章裡說只會print出1的結果跟我實測不太一樣,我這邊實測也是直接crash。
測到這邊就讓我想到為什麼上面DispatchQueue.global().async
裡包DispatchQueue.main.sync
就不會crash,serial.async { }
裡包一個sync{ }
就會crash?關鍵應該是在第一種其實是兩個不同Thread,本來就不會互相塞住,但第二種是加在同一個Serial Queue裡,會互相干擾。
不要造成互等的狀況。上面那段程式碼可以改成async非同步,彼此不互等。
let serial = DispatchQueue(label: "first")
serial.async {
print("1")
serial.async {
print("2")
}
print("3")
}
// console output:
// 1
// 3
// 2
DispatchWorkItem 是一種封裝 GCD 任務的方式。就是把程式碼包成一個task的感覺,再提交到queue來執行。他很有用的是他提供取消跟等待的操作,比直接把code寫在DisaptchQueue裡的Block又更有靈活度。
let workItem = DispatchWorkItem {
print("Work item is running.")
}
在當下的Thread直接同步執行。
workItem.perform()
不直接執行,提交到特定的queue來執行。
// 將工作項提交到後台隊列
DispatchQueue.global().async(execute: workItem)
沒辦法取消正在進行的任務,只能取消還沒執行的任務。
workItem.cancel()
同步的等待任務執行完。
workItem.wait()
例如:
用一個非同步的queue去執行,順序可能會掉換,看哪個先做到
let workItem = DispatchWorkItem {
print("Work item is running.")
}
DispatchQueue.global().async(execute: workItem)
print("123456")
// console output:
// 123456
// Work item is running.
// or
// Work item is running.
// 123456
加入wait後,123456就一定會在workItem執行完後才跑
DispatchQueue.global().async(execute: workItem)
workItem.wait()
print("123456")
// console output:
// Work item is running.
// 123456
等待一個任務完成,再執行下一個。
workItem.notify(queue: DispatchQueue.global(), execute: workItem2)
DispatchGroup 是 Swift 中的一種結構,它是 Grand Central Dispatch (GCD) 的一部分,用於追蹤一組任務的完成情況。你可以把多個任務添加到一個 DispatchGroup,然後等待他們全部完成,或者在他們全部完成時獲得通知。
我覺得這個用在可以把一堆任務綁成同一個群組,workItem是用在少量任務的情況,Group就用在執行多個任務。DispatchGroup也有通知/等待等操作可以使用。
let group = DispatchGroup()
// 把任務寫在queue的block中
DispatchQueue.global(qos: .userInitiated).async(group: group) {
print("Task 1 started")
// 做一些工作...
print("Task 1 finished")
}
手動標示一個任務加入group與完成。管理group任務的技術,背後是dispatch_group_enter
跟dispatch_group_leave
,兩個需要配對。
group.enter()
group.leave()
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)
// 進入Task1
group.enter()
queue.async(group: group) {
print("Task 1 started")
print("Task 1 finished")
group.leave() // 執行完離開
}
// 進入Task2
group.enter()
queue.async(group: group) {
print("Task 2 started")
print("Task 2 finished")
group.leave() // 執行完離開
}
group.notify(queue: DispatchQueue.main) {
print("All tasks are done!")
}
group.notify(queue: YOUR_QUEUE) {
print("All tasks are done!")
}
例如:
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)
// Task1
queue.async(group: group) {
print("Task 1 started")
print("Task 1 finished")
print(Thread.current)
}
// Task2
queue.async(group: group) {
print("Task 2 started")
print("Task 2 finished")
print(Thread.current)
}
group.notify(queue: DispatchQueue.main) {
print("All tasks are done!")
}
// console output:
// Task 1 started
// Task 2 started
// Task 2 finished
// <NSThread: 0x600001ff8200>{number = 5, name = (null)}
// Task 1 finished
// <NSThread: 0x600001ffcc80>{number = 6, name = (null)}
// All tasks are done!
不過我比較不懂要前面用enter/leave手動管理的意義,因為不寫的結果也是一樣的?
自問自答:如果queue.async{}的裡也是非同步的任務才有差別。
group.wait()
上面notify那段也可以改成
// ....做完Task1 & 2
group.wait()
print("All tasks are done! 2")
// console output:
// Task 1 started
// Task 2 started
// Task 2 finished
// <NSThread: 0x600001ff8200>{number = 4, name = (null)}
// Task 1 finished
// <NSThread: 0x600001ffcc80>{number = 5, name = (null)}
// All tasks are done! 2!
用wait要小心,因為notify是異步(async)通知不會阻塞Thread,wait是同步(sync)把Thread停住。
DispatchSemaphore 是 Swift 中用於控制並行執行的同步機制。它能夠限制並行任務的數量,常用於解決資源競爭或同步執行的情況。透過 DispatchSemaphore,你可以設定同時進入臨界區域的執行緒或任務數量,確保資源在同一時間只有特定數量的執行緒可以訪問,避免並行執行時出現的問題,例如資源競爭或資料不一致性。
Value傳入計數器的初始值,一開始不能小於0。
將值設為0對於兩個thread需要協調特定事件完成時很有用。將值設為大於0可以控制資源的使用。
必須是一對的。
func processTask(completion: (() -> Void)) {
print("Start processing some task......")
sleep(3)
completion()
}
func reloadUI() {
print("Start reload UI.")
}
// ...
let semaphore = DispatchSemaphore(value: 0)
processTask {
print("Task completed.")
semaphore.signal() // 任務完成
}
semaphore.wait() // 等待任務完成後,更新UI
reloadUI()
// console output:
// Start processing some task......
// Task completed.
// Start reload UI.
參考資料:
https://blog.csdn.net/zhangmengleiblog/article/details/108365032