GCD 神秘面紗揭開:讓你的 Swift 應用更強大

閱讀時間約 16 分鐘
GCD image by DALL·E 3

GCD image by DALL·E 3

這個浮誇標題是ChatGPT幫我想的😊 圖也是DALL幫我產的, AI萬歲!

GCD也是面試必問題啊!我答超爛的,只會用背景呼叫API+切Main Thread更新畫面,今天就來認真了解。

GCD是什麼?

先來問ChatGPT什麼是GCD?

GCD,全名 Grand Central Dispatch,是 Apple 提供的一種用於管理並行執行任務的技術。它是一種低階的 C API,用於優化應用程序以便在多核硬體上更有效地並行運行。

GCD 提供了一種簡單的方式來管理並行任務,而不需要直接管理線程的生命週期。它允許你將任務(也稱為“塊”或“閉包”)提交到不同的隊列(Queue)中進行執行。這些隊列可以是串行的(Serial,一次只執行一個任務)或並行的(Concurrent,可同時執行多個任務)。

簡單來說就是接受任務的queue。比較輕量級,簡單快速。

DispatchQueue

  1. FIFO(先到先執行)
  2. serially or concurrently
  3. 創造太多的Queue可能會造成Thread Explosion

基本用法

  • main: 調度Main Thread。
  • global: 全域的queue,可以用qos來調整執行的優先度。

把Code直接寫在block裡執行,不能等待也不能取消

DispatchQueue.global().async {    
// 一些比較花時間的操作
DispatchQueue.main.async {
// update UI
}
}

客製化的Queue

開發者自己創造的queue,最好是給他一個Label來識別(名稱寫法:com.xxx.xxxxx),因爲app裡會產生很多queue。預設是一次只執行一個任務,可以用attributes: .concurrent改成多個並行。

DispatchQueue(label: "com.custom.queue", attributes: .concurrent).async {
// 看你要做什麼
}

優先執行順序QoS

userInitiate > default > utility > background > unspecified

DispatchQueue.global(qos: .userInitiate).async {    
// 看你要做什麼
DispatchQueue.main.async {
// update UI
}
}

避免Thread Explosion

GCD有一個需要注意的點,為了要完成所有的任務,他有可能會一直創造Thread,最後造成App耗盡所有的Thread跟資源。通常發生在下列兩種狀況:第一,某個在cocurrent queue上的任務擋住現在的Thread,GCD會在創造另一個Thread去執行其他的任務。第二,創造太多custom queue,每個queue也都會創造自己的Thread。建議沒有特殊需求用global queue即可。

萬惡的Deadlock

這也是面試很愛考的。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了。

DispatchQueue.main.sync

DispatchQueue.main.sync

非同步裡再同步呼叫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。

crash!!

crash!!

async {} 裡包 sync {} 必定crash?

測到這邊就讓我想到為什麼上面DispatchQueue.global().async裡包DispatchQueue.main.sync就不會crash,serial.async { }裡包一個sync{ }就會crash?關鍵應該是在第一種其實是兩個不同Thread,本來就不會互相塞住,但第二種是加在同一個Serial Queue裡,會互相干擾。

如何避免Deadlock?

不要造成互等的狀況。上面那段程式碼可以改成async非同步,彼此不互等。

let serial = DispatchQueue(label: "first")

serial.async {
print("1")

serial.async {
print("2")
}

print("3")
}

// console output:
// 1
// 3
// 2


DispatchWorkItem

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

DispatchGroup 是 Swift 中的一種結構,它是 Grand Central Dispatch (GCD) 的一部分,用於追蹤一組任務的完成情況。你可以把多個任務添加到一個 DispatchGroup,然後等待他們全部完成,或者在他們全部完成時獲得通知。

我覺得這個用在可以把一堆任務綁成同一個群組,workItem是用在少量任務的情況,Group就用在執行多個任務。DispatchGroup也有通知/等待等操作可以使用。

創造一個Group,進queue執行

let group = DispatchGroup()
// 把任務寫在queue的block中​
DispatchQueue.global(qos: .userInitiated).async(group: group) {
print("Task 1 started")
// 做一些工作...
print("Task 1 finished")
}


enter/ leave: 標示group進入與離開

手動標示一個任務加入group與完成。管理group任務的技術,背後是dispatch_group_enterdispatch_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!")
}

Notify: 異步等待Group內任務全部執行完,發出通知

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{}的裡也是非同步的任務才有差別。

Wait: 同步等待前一個任務執行完

group.wait()

上面notify那段也可以改成

// ....做完Task​1 & 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

DispatchSemaphore 是 Swift 中用於控制並行執行的同步機制。它能夠限制並行任務的數量,常用於解決資源競爭或同步執行的情況。透過 DispatchSemaphore,你可以設定同時進入臨界區域的執行緒或任務數量,確保資源在同一時間只有特定數量的執行緒可以訪問,避免並行執行時出現的問題,例如資源競爭或資料不一致性。

init(value:)

Value傳入計數器的初始值,一開始不能小於0。

將值設為0對於兩個thread需要協調特定事件完成時很有用。將值設為大於0可以控制資源的使用。

Signal/wait

必須是一對的。

  • Signal: 任務完成後呼叫,計數器+1。如果前一個值是負數,這個function會叫醒在wait的Thread。
  • Wait:等待任務完成,計數器-1。如果此時為負數,他會等待signal發生。
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

https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/ios-gcd-dispatchgroup-dispatchsemaphore%E4%BB%8B%E7%B4%B9-ab4182c61dc5





avatar-img
7會員
35內容數
紀錄iOS開發上遇到的問題或是一些流程筆記。主要都是Swift。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Michelle Chen的沙龍 的其他內容
字數算法 = string.count? 在swift算一個string的字數時候,很直覺的會想到用.count來算 let s = "這是幾個字呢".count print(s.count) // 6 毫無疑問的安心信賴6個字 表情符號的場合 let emoji = "😂" print
Xcode15的 @escaping closure裡解包後的[weak self]不必再寫 self了!! 用一個範例class Test來實驗,裡面只有一個變數a跟一個testClosure回"test",再用callTestClosure把變數a改成"test" 編譯正常!變數a成功被改成
一樣先來看官方文件 A view controller that provides access to documents or destinations outside your app’s sandbox. 其實就是讓你去讀取檔案App的東西 有兩種模式,Don’t copy the do
iCloud+的服務 只有付錢的人才能用,免費仔如我從來沒聽過😗 簡介 通常你在瀏覽網頁時,網路供應商和你所造訪的網站可以看到網頁流量所包含的資訊(例如 DNS 記錄和 IP 位址)。此資訊可用來判斷你的身分,並建立描述檔來記錄你的位置以及往後的瀏覽記錄。 「iCloud 私密轉送」的設計旨
漸層文字 做出像Apple Keynote主題一樣的美美漸層文字 先畫出一大塊美美漸層色,可以選自己喜歡的顏色跟角度去做喔 LinearGradient(colors: [.blue, .purple, .red], startPoint: .bottomLead
先來看官方文件 https://developer.apple.com/documentation/swiftui/spacer Spacer A flexible space that expands along the major axis of its containing stack
字數算法 = string.count? 在swift算一個string的字數時候,很直覺的會想到用.count來算 let s = "這是幾個字呢".count print(s.count) // 6 毫無疑問的安心信賴6個字 表情符號的場合 let emoji = "😂" print
Xcode15的 @escaping closure裡解包後的[weak self]不必再寫 self了!! 用一個範例class Test來實驗,裡面只有一個變數a跟一個testClosure回"test",再用callTestClosure把變數a改成"test" 編譯正常!變數a成功被改成
一樣先來看官方文件 A view controller that provides access to documents or destinations outside your app’s sandbox. 其實就是讓你去讀取檔案App的東西 有兩種模式,Don’t copy the do
iCloud+的服務 只有付錢的人才能用,免費仔如我從來沒聽過😗 簡介 通常你在瀏覽網頁時,網路供應商和你所造訪的網站可以看到網頁流量所包含的資訊(例如 DNS 記錄和 IP 位址)。此資訊可用來判斷你的身分,並建立描述檔來記錄你的位置以及往後的瀏覽記錄。 「iCloud 私密轉送」的設計旨
漸層文字 做出像Apple Keynote主題一樣的美美漸層文字 先畫出一大塊美美漸層色,可以選自己喜歡的顏色跟角度去做喔 LinearGradient(colors: [.blue, .purple, .red], startPoint: .bottomLead
先來看官方文件 https://developer.apple.com/documentation/swiftui/spacer Spacer A flexible space that expands along the major axis of its containing stack
你可能也想看
Google News 追蹤
Thumbnail
徵的就是你 🫵 超ㄅㄧㄤˋ 獎品搭配超瞎趴的四大主題,等你踹共啦!還有機會獲得經典的「偉士牌樂高」喔!馬上來參加本次的活動吧!
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
在這篇文章中,我們將介紹工作與以前念書時期在開發流程上的差異,並深入瞭解CI/CD、Travis CI以及加解密的應用。 CI/CD是自動化的軟體開發實踐,而加解密則是保護機密資料安全的重要技術。
背後的機制是: 人類用戶:設定目標 AI Agent:定義任務排序任務 (Defining and Sequencing Tasks),執行任務 (Task Execution),評估 (Evaluation) 而這途中,AI Agent可以使用大語言模型,可以使用網路,可以使用其他工具。
Thumbnail
此章節旨在解釋Swift語言中函數的基本結構和操作方式,包括函數的聲明、呼叫、參數和返回值。閱讀這個章節可以幫助你理解並掌握如何在Swift編程中有效地使用和管理函數。
Thumbnail
本篇介紹了Swift程式語言中的各種流程控制元素,包括條件語句(如if, else if, else),三元運算子,多條件分支判斷的switch語句,以及各種迴圈(如for迴圈,while迴圈,以及repeat-while迴圈)。同時也詳細解釋了如何進行迴圈嵌套,以及如何使用控制迴圈語句。
Thumbnail
在IT世界中,持續整合(CI)和持續部署(CD)已成為提高開發效率和確保高質量軟件交付的重要實踐。本文將探討CI/CD的基本概念,介紹市場上的主流工具,並提供實施這一流程的指南和最佳實踐。
CI 持續整合 CI目的是建立自動化專案打包。 CD 持續發表 將打包完成的專案,自動發布。
※ 同步概念: 單純地「由上而下」執行程式碼,而且一次只執行一件事,也就是「按順序執行,一個動作結束才能切換到下一個」。缺點是你需要「等待」事情執行完畢,才能繼續往下走。 ※ 非同步概念: 盡可能讓主要的執行程序不需要停下來等待,若遇到要等待的事情,就發起一個「非同步處理」,讓主程序繼續執行,
Thumbnail
在Python中,queue是一個非常有用的模块。 它提供了多種佇列(queue)實現,用於在多線程環境中安全地交換信息或者數據。 佇列(queue)是一種先進先出(FIFO)的數據結構,允許在佇列的一端插入元素,另一端取出元素。(FIFO 是First In, First Out 的縮寫)
Thumbnail
當你需要在 Python 中執行多個任務,但又不希望它們相互阻塞時,可以使用 threading 模組。 threading 模組允許你在單個程序中創建多個執行緒,這些執行緒可以同時運行,從而實現並行執行多個任務的效果。
Thumbnail
關於多執行緒/多行程的使用方式 在Python 3.2版本之後加入了「concurrent.futures」啟動平行任務, 它可以更好的讓我們管理多執行緒/多行程的應用場景,讓我們在面對這種併發問題時可以不必害怕, 用一個非常簡單的方式就能夠處裡, 底下我們將為您展示一段程式碼: imp
Thumbnail
徵的就是你 🫵 超ㄅㄧㄤˋ 獎品搭配超瞎趴的四大主題,等你踹共啦!還有機會獲得經典的「偉士牌樂高」喔!馬上來參加本次的活動吧!
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
在這篇文章中,我們將介紹工作與以前念書時期在開發流程上的差異,並深入瞭解CI/CD、Travis CI以及加解密的應用。 CI/CD是自動化的軟體開發實踐,而加解密則是保護機密資料安全的重要技術。
背後的機制是: 人類用戶:設定目標 AI Agent:定義任務排序任務 (Defining and Sequencing Tasks),執行任務 (Task Execution),評估 (Evaluation) 而這途中,AI Agent可以使用大語言模型,可以使用網路,可以使用其他工具。
Thumbnail
此章節旨在解釋Swift語言中函數的基本結構和操作方式,包括函數的聲明、呼叫、參數和返回值。閱讀這個章節可以幫助你理解並掌握如何在Swift編程中有效地使用和管理函數。
Thumbnail
本篇介紹了Swift程式語言中的各種流程控制元素,包括條件語句(如if, else if, else),三元運算子,多條件分支判斷的switch語句,以及各種迴圈(如for迴圈,while迴圈,以及repeat-while迴圈)。同時也詳細解釋了如何進行迴圈嵌套,以及如何使用控制迴圈語句。
Thumbnail
在IT世界中,持續整合(CI)和持續部署(CD)已成為提高開發效率和確保高質量軟件交付的重要實踐。本文將探討CI/CD的基本概念,介紹市場上的主流工具,並提供實施這一流程的指南和最佳實踐。
CI 持續整合 CI目的是建立自動化專案打包。 CD 持續發表 將打包完成的專案,自動發布。
※ 同步概念: 單純地「由上而下」執行程式碼,而且一次只執行一件事,也就是「按順序執行,一個動作結束才能切換到下一個」。缺點是你需要「等待」事情執行完畢,才能繼續往下走。 ※ 非同步概念: 盡可能讓主要的執行程序不需要停下來等待,若遇到要等待的事情,就發起一個「非同步處理」,讓主程序繼續執行,
Thumbnail
在Python中,queue是一個非常有用的模块。 它提供了多種佇列(queue)實現,用於在多線程環境中安全地交換信息或者數據。 佇列(queue)是一種先進先出(FIFO)的數據結構,允許在佇列的一端插入元素,另一端取出元素。(FIFO 是First In, First Out 的縮寫)
Thumbnail
當你需要在 Python 中執行多個任務,但又不希望它們相互阻塞時,可以使用 threading 模組。 threading 模組允許你在單個程序中創建多個執行緒,這些執行緒可以同時運行,從而實現並行執行多個任務的效果。
Thumbnail
關於多執行緒/多行程的使用方式 在Python 3.2版本之後加入了「concurrent.futures」啟動平行任務, 它可以更好的讓我們管理多執行緒/多行程的應用場景,讓我們在面對這種併發問題時可以不必害怕, 用一個非常簡單的方式就能夠處裡, 底下我們將為您展示一段程式碼: imp