一個App如果完全是單線程開發的,那就意味著當有一段程序運行時間比較久的時候,就會造成主線程阻塞,也就導致了UI渲染過程會突然卡住的情況。比如說當我們進入一個畫面開始下載圖片的時候,如果圖片很大或者太多,就會造成等待的時候畫面直接不動了。這樣的設計會造成用戶體驗很差。
當一個App開啟進入運行狀態後就形成了一個進程。進程是系統進行資源分配和調度的一個單位,是一個運行中的程序。
一個進程可以有多個線程,而這些線程是共享這個進程的資源的。
iOS中提供了多種多線程的實現方式,這篇文章主要講GCD (Grand Central Dispatch)
iOS開發中常會遇到使用多線程(Multithreading),這裡想先從串行&併法(Serial & Concurrent)講起。
串行與並發
上圖所示,有兩種執行任務的方法,其中上面的我們成為串行(Serail),下面則是並發(Concurrent)
- 串行的特徵:一個任務接著一個任務執行,在一個任務完成前,不會執行其他任務。
- 併發的特徵:同時執行多個任務。
同步與異步
上圖中,左側為同步,右側為異步。
當Client端向Server端發送請求時(比如請求圖片),如果圖片很大,在圖片很大的情況下,就會需要進行一段時間的下載,而這個時候如果我們將下載圖片的任務放在主線程(main_queue)中執行的話,就會造成UI卡住不能動,直到下載完畢。
而如果我們採用右側的異步方式進行圖片下載,那麼UI就可以繼續進行渲染,當下在任務完成時,在進行圖片加載,不會阻塞到UI線程。
注:UI渲染是在主線程(main_queue)進行的。
隊列(Queue)
隊列同樣存在串行隊列與並行隊列,在隊列中的任務會依照隊列類型來執行任務(文中剛開始提到的,依次執行與同時執行任務)。
需要注意的是,當我們將隊列提交以後,GCD會負責找時間執行任務,唯一能保證的是,任務會按照隊列中的任務順序執行,但何時執行哪一個任務,是不知道的。
其中一個特別的隊列叫做main_queue,是一種串行隊列,也是唯一可以更新UI的線程(thread)
而系統也提供了四種並行隊列Global Dispatch Queues,按照有線級別不同分為background, low, default, high
相關術語
- 臨界區(Critical Section )
同一段代碼不能被不同的線程同時執行,有時候當同一個資源被多個線程同時處理的話會造資源內容不可靠(比如銀行帳戶,同時多個款項進出) - 死鎖(Dead Lock)
比如兩個線程要使用彼此所執行結果的內容,結果兩個線程都因對方沒辦法完成而導致自己無法完成,造成線程都卡住了。 - 線程安全(Thread Safe)
線程安全的Code可以被多個線程安全的調用而不會導致數據的不可靠或Crash。 - Context Switch
切換線程時儲存與恢復的執行狀態過程。
Code
創建隊列(Create Queue)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 主隊列 let queue = dispatch_get_main_queue() // 自行創建的隊列 // dispatch_queue_create("Queue名稱", 並行或者串行) // 串行 let queue = dispatch_queue_create("don_queue", nil) let queue = dispatch_queue_create("don_queue", DISPATCH_QUEUE_SERIAL) // 並行 let queue = dispatch_queue_create("don_queue", DISPATCH_QUEUE_CONCURRENT) // 全局並行隊列 let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) |
- DISPATCH_QUEUE_PRIORITY_HIGH 高
- DISPATCH_QUEUE_PRIORITY_DEFAULT 正常
- DISPATCH_QUEUE_PRIORITY_LOW 低
- DISPATCH_QUEUE_PRIORITY_BACKGROUND
任務
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// 同步任務 dispatch_sync(隊列, { () -> Void in // 執行內容 print(NSThread.currentThread()) }) // 異步任務 dispatch_async(隊列, { () -> Void in // 執行內容 print(NSThread.currentThread()) }) // 一個例子 dispatch_async(dispatch_get_main_queue(), { () -> Void in print("hi") }) |
對列祖(Group)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 創建隊列組 let group = dispatch_group_create() // 創建隊列 let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) // 多次調用方法 dispatch_group_async(group, queue) { () -> Void in for _ in 0..<3 { NSLog("group-01 - %@", NSThread.currentThread()) } } // 全部完成後的通知 dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in NSLog("完成 - %@", NSThread.currentThread()) } |
其他功能
dispatch_apply,執行指定次數的任務,執行起來會和dispatch_sync一樣是串行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
dispatch_async(transferQueue, { () -> Void in // 執行五次 dispatch_apply(5, self.transferQueue, { (index) -> Void in self.transferMoney(700) // 回到主線程,為了更新UI內容 dispatch_async(dispatch_get_main_queue(), { () -> Void in self.aProgressBar.progress += 0.2 }) }) }) |
- dispatch_group_async:用來監視一組任務是否執行完成
- dispatch_group_notify:用來在所有任務完成後發出通知,不會阻塞當前線程
- dispatch_group_wait:等待直到所有任務執行完畢才繼續執行後面的動作,會阻塞當前線程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
dispatch_group_async(transferGroup, transferQueue, { () -> Void in self.transferMoneySafely(3000) }) dispatch_group_async(transferGroup, transferQueue, { () -> Void in self.transferMoneySafely(3000) }) dispatch_group_async(transferGroup, transferQueue, { () -> Void in self.transferMoneySafely(3000) }) dispatch_group_async(transferGroup, transferQueue, { () -> Void in self.transferMoneySafely(3000) }) dispatch_group_async(transferGroup, transferQueue, { () -> Void in self.transferMoney(3000) }) // transferGroup中的任務都執行完成以後,發出通知 dispatch_group_notify(transferGroup, dispatch_get_main_queue(), {() -> Void in print("did transfer all money safely") }) // 阻塞線程,直到任務全部完成 dispatch_group_wait(transferGroup, DISPATCH_TIME_FOREVER) print("任務執行完成") |
互斥鎖
比如我們有一個轉帳功能的function叫做transferMoneySafely,我們先檢查是否有足夠的餘額進行轉帳,足夠的話就進行轉帳。
由於交易非常頻繁,可能我們進行轉帳的過程中,馬上又有轉帳的需求,在前一筆轉帳完成前,系統會誤以為餘額足夠,為了防止這樣的問題發生,就可以加入互斥鎖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// objc_sync_enter 與 objc_sync_exit 成對出現 func transferMoneySafely(money:Int) { objc_sync_enter(self) if myPocket - money >= 0 { // 處理轉帳需要時間 sleep(1) myPocket -= money print("💰did transfer money \(money)") } else { print("⚠️pocket has no enough money") } print("👝myPocket status is \(myPocket)") // 回到主線程,為了更新UI內容 dispatch_async(dispatch_get_main_queue(), { () -> Void in self.aProgressBar.progress += 0.2 }) objc_sync_exit(self) } |