GCD 多執行緒的說明與應用

法蘭克的 iOS 世界
9 min readJan 22, 2017

--

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 適合拿來處理共享的資源,因為這樣可以確保存取是按照順序來的。

Serial Queue
  • Concurrent(同時進行的)

相對於 Serial 就是同時發生的。也是說多個工作是同時被執行的。Concurrent Queues 代表這個 Queues 裡的工作會按順序「開始」執行,但因為是 Concurrent ,所以不必等上一個工作執行完才接著執行下一個,因此每個工作執行結果的時間是不可預測的。

Concurrent Queue

綜合 Synchronous 和 Asynchronous,加上 Serial、Concurrent 的 Queues 的行為,共有四種排列組合。

Synchronous 和 Asynchronous,加上 Serial、Concurrent 的 Queues 的行為,共有四種排列組合

根據上圖,我們得知:

  1. 只有 Async 才會開啟新的執行緒
  2. 在 Serial Queue 下,只會開啟一條執行緒。
  3. 在 Serial Queue 下,不管是 sync 或 async 任務都是依序執行的。
  4. 在 Concurrent Queue 下,用 Async 執行 Tasks 會開啟多條執行緒
  5. 在 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 會依序執行

Serial Queue
Concurrent Queue

比較 sync 和 async 的差異

驗證 sync 和 async 的執行結果。不管是 serial 或 concurrent queue,在 sync block 裡的 task 都是會依序執行的,反之亦然

sync block
async block

Concurrent queue 加上非同步執行時,下一個迴圈不會等到上一個迴圈執行完才開始執行,且每個 async block 都會開啟不同的執行緒。

每個 async block 都會開啟不同的執行緒
下一個迴圈不會等到上一個迴圈執行完才開始執行

dispatch groups 介紹及應用

dispatch groups 提供更方便的等待機制,當我們必須等待一個或一個以上的 thread 處理完工作後,再去執行某件事時,就可使用此機制來達成,不過 dispatch groups 有兩種狀況,一是 queue 裡的工作項目都是很單純的個別子執行緒;二是 queue 裡的工作項目除了都是個別的子執行緒外,每個子執行緒裡又有子執行緒,這種狀況實際上非常常見,例如在子執行緒裡又去呼叫後端的 API(後端 API 屬另一條子執行緒),此時就要用比較特殊的作法來處理,以下就各別針對這兩種情況來說明。

▼queue 裡的工作項目都是很單純的個別子執行緒

queue 都是在主執行緒執行

▼queue 裡的工作項目除了都是個別的子執行緒外,每個子執行緒裡又有子執行緒(後端 API 屬另一條子執行緒),此情境就必須得搬出 enter() 和 leave() 方式來告訴 compile 何時開始呼叫和結束 API 的呼叫。

queue 裡又使用非同步的方式去呼叫後端的 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 的

DispatchQueue.global 產生的 Queues 是 Concurrent 的

如果您喜歡我的文章,請多按幾下「拍手」給我鼓勵,或是按「follow」讓我持續提供好文章給您。

--

--

Responses (2)