GCD 多執行緒的說明與應用
GCD(Grand Central Dispatch) 是 Apple 將複雜且不易使用的 thread 操作方式簡化過的 API,雖然使用上相對於傳統的 thread 會有些限制,但整體來說輕鬆許多也較安全。 總而言之,GCD為多執行緒的運用,其作用在於可將單一任務拆成多個小任務同時執行,亦或者同時執行多個任務,目的在於縮短執行任務的時間,而最重要的是,要確保不要佔用主執行緒而影響到使用者的體驗
。而多執行緒的應用頗多,諸如:
- 處理非同步下載圖片的工作。
- 非同步處理大量的資料同步,若是只有一個主執行緒在處理是不夠的,此時可以開 1~ n 個的執行緒來縮短其同步的時間。
- 可得知同時呼叫多支 API 都完成的時間點。
- 確保不要佔用主執行緒。例如從 A 頁面切換至 B 畫面,在 B 頁面顯示前必須載入大量的資料,此時若是沒有另開一個子執行緒去負責載入資料的話,畫面則會會卡在 A 頁面等待 B 頁面要用到的資料完全載入後,頁面才會切換過去的,如此,使用者的第一反應或許會覺得是不是壞了,輕者可能會遭到客訴,嚴重的話,可能馬上就將我們辛苦撰寫的 App 刪除掉。所以,正確的做法就是另開一個子執行緒負責去載入資料,主執行緒則要是保持順暢的(A 頁面切換至 B 頁面),並且在 B 頁面上放置一個 loading 的元件讓使用者覺得正在載入資料中。
以上即是常用的多執行緒應用,但在開始撰寫範例前,有幾個有關於執行緒的觀念和專有名稱必須先了解,確保在實作過程中能更清楚明白。
名詞解釋
Queues
Queues 是資料結構中的一種型態,翻成中文叫「佇列」。它的特性是先進先出(FIFO, First-in First-out)。
Synchronous vs Asynchronous
這兩個名詞有反義的概念,主要是在描述一個函數回傳資料的行為。
- Synchronous(同步)
一個 Synchronous 的 Function 只有在完成裡面的工作後,才會回傳值。
- Asynchronous(非同步)
相對於 synchronous,asynchronous 的 function 會馬上回傳值。asynchronous function 裡的工作會按照順序執行,但這個 function 不會等其它的動作執行完,它會馬上回傳值,因此 asynchronous function 不會造成它所在的執行緒阻塞。
Serial vs Concurrent
這兩個名詞有反義的概念,主要是在描述當每項工作(Task)被執行時,跟其他工作的關係。
- Serial(照順序排列的)
Serial Queues 的意思就是這個佇列裡的工作是按照順序執行的,一次只執行一個,當前一個執行完後,才會執行下一個。Serial Queues 適合拿來處理共享的資源,因為這樣可以確保存取是按照順序來的。
- Concurrent(同時進行的)
相對於 Serial 就是同時發生的。也是說多個工作是同時被執行的。Concurrent Queues 代表這個 Queues 裡的工作會按順序「開始」執行,但因為是 Concurrent ,所以不必等上一個工作執行完才接著執行下一個,因此每個工作執行結果的時間是不可預測的。
綜合 Synchronous 和 Asynchronous,加上 Serial、Concurrent 的 Queues 的行為,共有四種排列組合。
根據上圖,我們得知:
- 只有 Async 才會開啟新的執行緒。
- 在 Serial Queue 下,只會開啟一條執行緒。
- 在 Serial Queue 下,不管是 sync 或 async 任務都是依序執行的。
- 在 Concurrent Queue 下,用 Async 執行 Tasks 會開啟多條執行緒。
- 在 Concurrent Queue 下,用 Async 執行 Tasks 是速度最快的方式,但因為 Tasks 是同時進行的,所以,必須要考量需求是否有先後執行順序的問題。
內容大鋼
- 比較 serial 和 concurrent queue 的差異
- 比較 sync 和 async 的差異
- dispatch groups 介紹及應用
- Quality of Service(QoS)介紹及應用
- 延遲執行介紹及應用
- 使用更簡單的方式來取得主執行緒和子執行緒非同步的 queue
比較 serial 和 concurrent queue 的差異
▼建立 serial queue 和 async 的 block
第 2 行 => 建立 serial queue,預設即是 serial queue。
第 5 行 => 建立 async 的 block。async 狀態下,才能測試 serial 和 concurrent 的差異。
▼建立 concurrent queue 和非同步的 block
第 2 行 => 建立 concurrent queue。
第 5 行 => 建立 async 的 block。async 狀態下,才能測試 serial 和 concurrent 的差異。
▼驗證 serial queue 的執行結果。不管是 sync 或 async 下,因只有一條 thread,又是有順序性的,所以,queue 裡的 task 會依序執行
比較 sync 和 async 的差異
驗證 sync 和 async 的執行結果。不管是 serial 或 concurrent queue,在 sync block 裡的 task 都是會依序執行的,反之亦然
Concurrent queue 加上非同步執行時,下一個迴圈不會等到上一個迴圈執行完才開始執行,且每個 async block 都會開啟不同的執行緒。
dispatch groups 介紹及應用
dispatch groups 提供更方便的等待機制,當我們必須等待一個或一個以上的 thread 處理完工作後,再去執行某件事時,就可使用此機制來達成,不過 dispatch groups 有兩種狀況,一是 queue 裡的工作項目都是很單純的個別子執行緒;二是 queue 裡的工作項目除了都是個別的子執行緒外,每個子執行緒裡又有子執行緒,這種狀況實際上非常常見,例如在子執行緒裡又去呼叫後端的 API(後端 API 屬另一條子執行緒),此時就要用比較特殊的作法來處理,以下就各別針對這兩種情況來說明。
▼queue 裡的工作項目都是很單純的個別子執行緒
▼queue 裡的工作項目除了都是個別的子執行緒外,每個子執行緒裡又有子執行緒(後端 API 屬另一條子執行緒),此情境就必須得搬出 enter() 和 leave() 方式來告訴 compile 何時開始呼叫和結束 API 的呼叫。
Quality of service(QoS)介紹及應用
QoS(QoSClass) 是一個可用來定義執行緒執行先後順序的 enum,若是沒定義也有會 default 值,當然主執行緒的優先權是在最前面,再來才會是子執行緒,而其 QoSClass enum 定義的名稱執行先後順序如下:
userInteractive > userInitiated > `default` > utility > background > unspecified
執行結果如下(queue2 因為是最低順位所以它只會穿插執行):
延遲執行介紹及應用
有時侯我們想在兩秒之後去執行某個事情的話,我們就可以使用 asyncAfter(deadline:execute:) 來延遲執行的動作,deadline 為秒數,而 execute 為一 block。
第 4 行 => 表示系統時間 2 秒後開始執行 block 裡的動作,DispatchTime.now() 可取得系統的時間,。
執行結果如下:
使用更簡單的方式來取得主執行緒和子執行緒非同步的 queue
如前述,要取得執行緒都得先必須生成 DispatchQueue 物件,而其實不必這麼麻煩的,我們可以透過更簡單的方式來取得,只是要注意盡量不要濫用。
▼取得非同步的主執行緒 queue
透過 DispatchQueue.main 來取得主執行緒的使用權,block 即可撰寫我們想要做的事,在實務上通常被用來更新 UI,因更新 UI 只能放在主執行緒。
▼取得非同步的子執行緒 queue
值得一提的是,DispatchQueue.global 產生的 Queues 是 Concurrent 的
如果您喜歡我的文章,請多按幾下「拍手」給我鼓勵,或是按「follow」讓我持續提供好文章給您。