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





6會員
35內容數
紀錄iOS開發上遇到的問題或是一些流程筆記。主要都是Swift。
留言0
查看全部
發表第一個留言支持創作者!
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
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
在東南亞料理的馥郁香氣中,總有一抹獨特的辛辣,那是來自於「南薑」的魅力。它形似薑,卻又帶有肉桂和胡椒的辛香,是泰式咖哩、冬蔭功湯等經典菜餚的靈魂所在。然而,南薑的用途遠不止於烹飪,它更是一種歷史悠久的藥用植物,蘊藏著豐富的養生智慧。讓我們一起踏上探索之旅,揭開這神秘香料的面紗,領略它在烹飪和養生世界
Thumbnail
Insert Greatest Common Divisors in Linked List 題目給定一個鏈結串列, 請在兩兩節點之間加入一個新節點,新節點的值為兩者之間的最大公因數。 最後返回新串列的head node作為答案。
Thumbnail
本文章討論瞭如何補修SCPC/GCDF證書的學分,提供了多種快速和經濟的修學方案,並介紹了具體的課程和報名連結。文章中還溫馨地提到了持續學習計畫不會受到大地震的影響。
Thumbnail
因為身為公司人資也有接觸招募的關係,閒聊之間身邊許多朋友會好奇詢問 到底在面試後,公司是怎麼錄取哪位候選人呢? 這題很難三言兩語道盡,但如果用一句話概括的話,我會說 「公司會選擇在有需求的當下最適合我們的候選人——尋找那位有緣人」
Thumbnail
這個浮誇標題是ChatGPT幫我想的😊 圖也是DALL幫我產的, AI萬歲! GCD也是面試必問題啊!我答超爛的,只會用背景呼叫API+切Main Thread更新畫面,今天就來認真了解。 GCD是什麼? 先來問ChatGPT什麼是GCD? GCD,全名 Grand Central Dis
Thumbnail
這句話可能會在日本觀光網站、電視新聞播報或一些傳記暢銷書中遇到。來學學怎麼用日文說揭開不為人知的一面。
Thumbnail
解密煙罩維修:揭開神祕面紗,發現無法繞過的難點之「維修運水煙罩」 尊敬的讀者們,今天我將和大家分享一些關於維修運水煙罩時最容易出現問題的部分,並向大家介紹一傢俱備實力的維修公司——駿弘電器維修。這將是一場帶你撥開迷霧,解開謎題的精彩冒險之旅! 作為一個維修技師,我對煙罩的問題並不陌生
Thumbnail
章節一:七月傳說與股市操縱 在這一章節中,首先介紹農曆七月的鬼月習俗和相傳的鬼故事,如中元普渡、放水燈等。 接著,揭示了在這神秘時刻,有人竟然利用這股迷信力量操縱股市的真實案例。 例如,某些投資者可能會在鬼月期間散佈謠言,操縱特定類股的價格,或在特定日子進行大量交易以影響市場走勢。 輔助工具
Thumbnail
鬼月 充滿神秘色彩的農曆七月,人們往往忍不住談論著鬼故事和習俗。然而,你可曾想過這神秘的七月與股市居然存在著奇妙的關聯? 七月傳說與股市操縱 講述農曆七月的鬼月習俗和相傳的鬼故事。同時,我們也將揭示在這神秘時刻,有人竟然利用這股迷信力量操縱股市,引發了一系列驚人的事件。 鬼月股市狂潮與類股崛
Thumbnail
提到徵信社大部分華人可能只想到調查外遇、跟蹤、抓姦等感情相關問題,但其實徵信概念最早源自17世紀的英國,還跟私家偵探有關,其業務涵蓋範圍之廣也是一般人較少接觸的,話雖如此,一提到徵信社,「大愛徵信」絕對會馬上浮現在一般人的腦海,但大愛徵信社是如何開啟徵信這個事業?「大愛」這個名字又從何而來?
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
在東南亞料理的馥郁香氣中,總有一抹獨特的辛辣,那是來自於「南薑」的魅力。它形似薑,卻又帶有肉桂和胡椒的辛香,是泰式咖哩、冬蔭功湯等經典菜餚的靈魂所在。然而,南薑的用途遠不止於烹飪,它更是一種歷史悠久的藥用植物,蘊藏著豐富的養生智慧。讓我們一起踏上探索之旅,揭開這神秘香料的面紗,領略它在烹飪和養生世界
Thumbnail
Insert Greatest Common Divisors in Linked List 題目給定一個鏈結串列, 請在兩兩節點之間加入一個新節點,新節點的值為兩者之間的最大公因數。 最後返回新串列的head node作為答案。
Thumbnail
本文章討論瞭如何補修SCPC/GCDF證書的學分,提供了多種快速和經濟的修學方案,並介紹了具體的課程和報名連結。文章中還溫馨地提到了持續學習計畫不會受到大地震的影響。
Thumbnail
因為身為公司人資也有接觸招募的關係,閒聊之間身邊許多朋友會好奇詢問 到底在面試後,公司是怎麼錄取哪位候選人呢? 這題很難三言兩語道盡,但如果用一句話概括的話,我會說 「公司會選擇在有需求的當下最適合我們的候選人——尋找那位有緣人」
Thumbnail
這個浮誇標題是ChatGPT幫我想的😊 圖也是DALL幫我產的, AI萬歲! GCD也是面試必問題啊!我答超爛的,只會用背景呼叫API+切Main Thread更新畫面,今天就來認真了解。 GCD是什麼? 先來問ChatGPT什麼是GCD? GCD,全名 Grand Central Dis
Thumbnail
這句話可能會在日本觀光網站、電視新聞播報或一些傳記暢銷書中遇到。來學學怎麼用日文說揭開不為人知的一面。
Thumbnail
解密煙罩維修:揭開神祕面紗,發現無法繞過的難點之「維修運水煙罩」 尊敬的讀者們,今天我將和大家分享一些關於維修運水煙罩時最容易出現問題的部分,並向大家介紹一傢俱備實力的維修公司——駿弘電器維修。這將是一場帶你撥開迷霧,解開謎題的精彩冒險之旅! 作為一個維修技師,我對煙罩的問題並不陌生
Thumbnail
章節一:七月傳說與股市操縱 在這一章節中,首先介紹農曆七月的鬼月習俗和相傳的鬼故事,如中元普渡、放水燈等。 接著,揭示了在這神秘時刻,有人竟然利用這股迷信力量操縱股市的真實案例。 例如,某些投資者可能會在鬼月期間散佈謠言,操縱特定類股的價格,或在特定日子進行大量交易以影響市場走勢。 輔助工具
Thumbnail
鬼月 充滿神秘色彩的農曆七月,人們往往忍不住談論著鬼故事和習俗。然而,你可曾想過這神秘的七月與股市居然存在著奇妙的關聯? 七月傳說與股市操縱 講述農曆七月的鬼月習俗和相傳的鬼故事。同時,我們也將揭示在這神秘時刻,有人竟然利用這股迷信力量操縱股市,引發了一系列驚人的事件。 鬼月股市狂潮與類股崛
Thumbnail
提到徵信社大部分華人可能只想到調查外遇、跟蹤、抓姦等感情相關問題,但其實徵信概念最早源自17世紀的英國,還跟私家偵探有關,其業務涵蓋範圍之廣也是一般人較少接觸的,話雖如此,一提到徵信社,「大愛徵信」絕對會馬上浮現在一般人的腦海,但大愛徵信社是如何開啟徵信這個事業?「大愛」這個名字又從何而來?