iOS 多线程技术总结
概覽
- 進程與線程的概念
- 多線程的由來
- 并行與并發
- 多線程的實現
- 串行與并行
- 線程的幾種狀態
- 串行隊列與并發隊列區別
- iOS 實現多線程的幾種方法(NSOperation/GCD)
- GCD線程阻塞的幾種情況
- NSOperation 與 GCD 的關系以及 NSOperation 的使用、實現
- iOS 中是怎么定義多線程的?或者說什么情況下是多線程的?
- pThread、NSThread、GCD、NSOperation 之間的關系
- 串行隊列與主隊列的區別
- 全局隊列與并發隊列的區別
- 調度組的作用以及使用
進程與線程
進程是運行中的程序,線程是進程的子集,具有單個處理任務的能力,進程通過對線程的包裝,通過時間輪轉算法間接實現了程序處理任務的并發,進而多線程技術應運而生。
由于計算機程序處理任務的是按照串行處理的方式進行,如果程序以線程為基準去處理一些任務,假如處理的任務比較耗費時間,例如對與 IO 的操作,那么由于串行處理機制,在時間輪轉分發到某個進程的時候,恰巧該進程假如正在處理 IO 流耗時操作的話,如果沒有線程的存在,那么該進程就會始終處于處理 IO 操作的過程中,程序就會因此而假死,此時都在等待 IO 操作,而 cpu 此時由于沒有其他任務處理,所以也是閑置狀態,導致整體cpu效率變低。為了保證這些耗時操作能夠獨立去某個地方去執行而同時又不影響其他任務的正常執行,提出了多線程的處理方案,讓程序運行的時候拆分成多個任務,這些任務分別去分發到不同的地方去單獨處理,這些單獨的地方就是線程,不同線程包裝不同的任務,這樣就解決了因為耗時操作而導致的cpu閑置,運行效率變低的問題。
多線程技術的實現
多線程是通過時間輪轉算法實現的并發
并發與并行的區別
并行是并發的子集,并行從硬件層面上實現了多線程,所謂的硬件就是一些廠商經常宣傳的工藝,幾核幾線程工藝,從硬件上直接實現了并行。并發實現的另一種方式就是多線程技術,是從軟件層面上實現的并發。
NSOperation 與 GCD 的關系以及 NSOperation 的使用、實現
首先, NSOperation是一個抽象類,本身不能通過實例去實現它。其次,NSOperation 是對 GCD 的封裝,在原有基礎上又添加了一些線程的操作(GCD 中沒有的 api), 例如取消線程任務、判斷線程的執行狀態以及控制線程的數量等,這些都是 GCD 不對外暴露的,所以一些三方的框架例如 AFNetworking、SDWebImage 等框架都是使用的 NSOperation 進行的封裝。如果想精確的對線程進行操作的話,NSOperation 更適合去進行對應的相關操作。
NSOperation 的實例是通過兩個子類進行實現的,分別為 NSBlockOperation 、NSInvocationOperation。兩者的區別是 NSBlockOperation 是通過 Block 形式添加任務、而 NSInvocationOperation 是通過方法的形式去添加任務的。除此之外,NSBlockOperation start 之后是并發執行添加的任務的。而 NSInvocationOperation 是非并發執行任務的。 一般情況下,我們需要將創建的線程放在 NSOperationQueue 中去自動執行任務,如果不放在 NSOpertionQueue 中,直接通過手動的 Start 方法去執行的話,那么默認的執行操作是在當前線程中執行任務的,如果執行任務,需要當前的線程處于 ready 狀態,如果不在改狀態去執行任務的話,系統會拋出異常。如果想在子線程中去執行操作,需要手動去將其放在子線程中執行。也可以通過子類繼承 NSOperation 重寫 main 方法來實現子線程操作。這樣的操作都很麻煩,而且很容易出錯,除非特別需要,我們一般都使用 NSOperationQueue 進行NSOperation 的管理。
往NSOperationQueue 中添加NSOperation 的時候,默認會在子線程自動執行任務(直接執行或者間接通過 GCD 執行)
pThread、NSThread、GCD、NSOperation之間的關系可以用圖形的形式表示如下
iOS 中是怎么定義多線程的?或者說什么情況下是多線程的?
當有任意的線程從主線程分離出去的時候,App被認為是多線程的,可以通過 isMultiThread 來判斷當前 App是否是多線程狀態。只要某個子線程被創建后(不是 NSThread 對象),不需要正在運行,就認為是多線程狀態。
并發隊列與串行隊列的區別
并發隊列是派發到隊列中的任務并發執行,而串行隊列是指派發到隊列中的任務順序執行,派發到隊列中的任務執行完成之后才能夠繼續執行派發的下一個任務。
并發隊列可以同時執行多個任務,多個任務的執行受限于當前的派發方式(同步派發與異步派發)。
并發隊列同步派發會由于同步原因(阻塞當前線程)會執行同步任務直到其完成才能夠執行下一條分發的任務。每次只能夠執行單一的任務(同步造成當前隊列中只有一個任務存在,盡管是并發隊列,每次也只能夠執行一個任務)。
而并發隊列異步派發是同時派發了多個任務,而派發的任務因為是異步派發的,所以同時執行的任務是多個(隊列中異步派發了多個任務,所以并行隊列能夠同時執行多個任務,此時需要新開多個線程處理任務)。
注意: 串行隊列,每次只執行一個任務,直到當前的任務執行完成之后,再去執行下一個任務。 并發隊列,每次執行多個任務,所以多個任務的執行完成順序不能夠確定(通過開啟多個線程實現的)。
任務派發與不同隊列執行的情況
下面的表格總結的很詳細
它們的區別可以總結如下:
- 同步派發:當前任務不執行完成,不會執行下一條任務
- 異步派發:當前任務執行過程中,同樣可以執行下一條任務
- 串行隊列:必須等待第一個任務執行完成以后,再去調度另一個任務
- 并發隊列:同時調度多個任務,至于這些任務通過幾個線程去執行是由 GCD 管理
- 主隊列:全局串行隊列,由主線程串行調度,并且有且只有一個
- 全局隊列:沒有名稱的并發隊列
結合例子:
串行隊列同步任務
/**串行隊列 同步任務//串行隊列同步分發任務,阻塞當前的線程,不需要開啟新的線程去執行//受限于串行隊列與同步分發的特點,同步分發的任務如果當前的任務不執行完那么就不會去執行下一條任務//串行隊列則是每次執行分發一條的任務,當前任務完成之后才能夠繼續執行下一條任務*/ - (void)serialSyncTask{dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);for (int i = 0; i<4; i++) {dispatch_sync(queue, ^{NSLog(@"串行隊列同步分發任務-Task(%d),currentThead:%@",i,[NSThread currentThread]);});NSLog(@"分發 Task(%d) 完成",i);}}復制代碼串行隊列異步任務
/**串行隊列 異步任務//串行隊列異步分發任務,不阻塞當前的線程,所以隊列中同一時間分發了三個任務,按照先進先出原則,//任務按順序開始,由于是串行隊列,在執行某個任務的時候,是不會去調度執行其他任務的,所以此時依次//按照隊列中的任務執行完成操作。//主隊列異步分發任務,不阻塞隊列中其他任務的執行*/ - (void)serialAsyncTask{/******************************串行隊列異步分發任務*********************************************/dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);for(int i = 0; i<4; i++){dispatch_async(serialQueue, ^{sleep(i+1);NSLog(@"串行隊列異步分發任務-Task(%d),currentThead:%@",i,[NSThread currentThread]);});NSLog(@"分發 Task(%d) 完成",i);}NSLog(@"執行結束");}復制代碼運行結果如下:
并發隊列同步任務
/**并發隊列 同步任務//阻塞當前線程//并行隊列同一時將任務分發到隊列中,同步執行需要當前的任務完成之后才能繼續執行其他的任務,是按照順序進行的,所以此時不會新建線程去處理任務*/ - (void)concurrentSynac{dispatch_queue_t queue = dispatch_queue_create("concorrentQueue", DISPATCH_QUEUE_CONCURRENT);for (int i = 0; i<4; i++) {dispatch_sync(queue, ^{sleep(1);NSLog(@"并發隊列同步分發任務-Task(%d),currentThead:%@",i,[NSThread currentThread]);});NSLog(@"分發 Task(%d) 完成",i);}}復制代碼并發隊列 異步任務
/**并發隊列 異步任務不阻塞當前線程由于隊列中異步添加了多個任務,并發隊列同一時間能夠執行多個任務,所以需要新建多個線程去處理隊列中的任務*/ - (void)concurrentAsynac{dispatch_queue_t queue = dispatch_queue_create("concorrentQueue", DISPATCH_QUEUE_CONCURRENT);for (int i = 0; i<4; i++) {dispatch_async(queue, ^{sleep(1);NSLog(@"并發隊列異步分發任務-Task(%d),currentThead:%@",i,[NSThread currentThread]);});NSLog(@"分發 Task(%d) 完成",i);}}復制代碼死鎖的發生
如果當前隊列中 task0正在執行操作,此時如果同步分發任務task1,同步分發阻塞當前線程,從而會執行當前的任務直到當前分配的任務結束,但是,因為是串行隊列,串行隊列中每次只能執行一個任務,由于 task0沒有執行完成,所以 task1需要等待 task0執行完成才能夠執行。由于task1要想執行需要等到task0執行完成,而 task0中由于同步分發了task1,需要等到 task1執行完成才能夠繼續完成當前的任務,兩者相互等待對方任務執行完成,最終造成死鎖。
相同的情況,主隊列同步任務也會造成死鎖,具體原因也很類似。
原因:主隊列是全局串行隊列,如果同步執行任務的話,由于主隊列當前的任務沒有完成此時是不會調度當前分發的任務的,而此時分發的任務又是同步的,同步分發的任務有個特點就是阻塞當前線程,執行當前的任務知道結束。所以由于上一個任務始終由于分發導致其完成不了,分發的任務又一直在等待其完成,兩者造成了一個死循環,不斷在等待對方完成,卻永遠都完成不了,導致死鎖的發生。
簡潔的說:
- 主隊列:如果主線程正在執行代碼,就不調度任務
- 同步任務:如果隊列中前一個任務沒有執行完成,就繼續等待上一個任務完成再去執行下一個任務
- 兩者相互等待造成死鎖
全局隊列與并發隊列的區別
- 全局隊列是沒有隊列名字的,并發隊列是有名字的。而有名字的隊列是可以跟蹤到的
- 一般使用全局隊列
- 在 MRC 中需要手動管理內存,并發隊列是通過 creat 出來的,在 MRC 中見到creat就需要 release 操作,而全局隊列不需要 release 操作,全局隊列有且只有一個
同步任務的用途
一般一些耗時操作會放在一個線程中執行,而當前的這個操作可能需要一些『依賴』關系的操作之后才能夠執行,這時候就需要同步的操作了。比如一個界面的展示需要兩個接口數據的返回才能夠正常展示,那么此時需要同步兩個接口的數據,然后最后才去展示數據內容,對于前兩個接口的請求數據我們需要將其操作通過同步分發的操作保證每一步執行完成之后在進行下一步操作。如下代碼:
- (void)syncThreadUse{//全局并發隊列 異步調度派發dispatch_async(dispatch_get_global_queue(0, 0), ^{//并發隊列,同步派發dispatch_sync(dispatch_get_global_queue(0, 0), ^{sleep(3);NSLog(@"請求接口一,currentThread:%@",[NSThread currentThread]);});//并發隊列,同步派發dispatch_sync(dispatch_get_global_queue(0, 0), ^{sleep(1);NSLog(@"請求接口二,currentThread:%@",[NSThread currentThread]);});//并發隊列,異步派發dispatch_async(dispatch_get_global_queue(0, 0), ^{NSLog(@"異步派發,currentThread:%@",[NSThread currentThread]);/*注意:此時用dispatch_sync 與 dispatch_async效果一樣dispatch_sync并不造成死鎖,因為造成死鎖的原因是隊列中分發的任務與當前隊列中執行的任務相互等待造成的死鎖此時任務的分發由于不在dispatch_get_main_queue()主線程隊列中,所以并沒有讓分發的任務等待主線程正在執行任務結束的情況存在,依據主線程隊列的執行任務的特點在主線程已經執行完成任務之后才會去執行當前的任務,來執行當前任務dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"展示界面");});*///如果對死鎖不清楚的話,建議使用dispatch_async方式進行任務的派發,從而避免死鎖的發生dispatch_async(dispatch_get_main_queue(), ^{NSLog(@"展示界面");});});}); }復制代碼結果如下:
調度組的作用以及使用
Dispatch Group 調度組
- Dispatch Group 在添加到組里面所有的任務完成的時候發出通知,這些任務可以是同步的,也可以是異步的,哪怕是不同的隊列。
- Dispatch Group只有異步執行
- 創建的 Dispatch Group,像是一個未完成的計數器
- dispatch_group_enter 手動通知 Dispatch Group 任務已經開始。它與 dispatch_group_leave 成對出現
- Dispatch Group 可以包含不同的隊列類型,包括:自定義串行隊列、主隊列(全局串行隊列)、并發隊列
Dispatch Group 的幾個函數
- dispatch_group_wait 會阻塞當前的線程,知道組里面所有的任務都完成或者等到某個超時完成。 如果所在的任務完成前超時了,該函數會返回一個非零值??梢詫Υ朔祷刂底鰲l件判斷來確定是否超出了等待周期。 DISPATCH_TIME_FOREVER 讓這個組永遠等待。
- dispatch_apply 類似于 for 循環,能夠并發的執行不同的迭代。 這個函數是同步執行的,只會在所有工作完成之后才能返回。 對于并發循環使用 dispatch_apply 可以幫助我們追蹤任務的進度
- dispatch_group_notify 異步方式進行 不會阻塞任何線程 有時候需要在多個異步任務都執行完成之后后續做一些操作
調度組的使用:
- (void)dispatchGroup{dispatch_group_t group = dispatch_group_create();dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{sleep(6);NSLog(@"請求接口1");});dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{NSLog(@"請求接口2");});dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{NSLog(@"請求接口3");});dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"請求接口全部完成");});}復制代碼參考文章: iOS中的多線程技術 多線程之GCD
總結
以上是生活随笔為你收集整理的iOS 多线程技术总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux中的一个命令行计算器bc简介
- 下一篇: JS学习--Date对象