iOS多線程總結

前言:本文章摘自作者devsongxx,鏈接:http://www.itdecent.cn/p/8ff1eaebbc6e (著作權歸作者所有。商業(yè)轉載請聯系作者獲得授權,非商業(yè)轉載請注明出處。),從多線程的基本概念,進程的概念,引出iOS中的四種多線程方案pthread、NSThread、NSOperation和GCD,每一部分都有詳細的代碼和解釋說明;在GCD中,引出同步、異步、串行隊列(包括主隊列)和并發(fā)隊列概念,并對他們的六種組合進行詳細的代碼驗證和說明,把這些概念安排的明明白白,然后詳細的說明了GCD常見的其他用法;最后,對iOS中線程安全的方案進行全方面的介紹說明并且配上示例代碼。

一、多線程概念

1、多線程概念:

? 一個進程中可以開啟多條線程,每條線程可以并行執(zhí)行不同的任務。多線程可以充分的利用多個CPU同時處理任務,提高程序的執(zhí)行效率。

2、進程概念:

? 進程是指在系統(tǒng)中正在運行的一個應用程序,每個進程之間是獨立的。而線程是應用程序中一條任務的執(zhí)行路徑。

3、進程和線程的關系:

? 1)一個進程可以包含多個線程;

? 2)一個進程中的所有任務都是在線程中執(zhí)行;

4、iOS多線程實現方案:

? 1)pthread:純C語言API,是一套通用的多線程API,適用于Linux、Unix、Windows等系統(tǒng),線程生命周期由程序員管理。在iOS實際開發(fā)中,使用較少。

? 2)NSThread:使用更加面向對象,并可直接操作線程對象,線程生命周期由程序員管理,項目開發(fā)中使用較多;

? 3)NSOperation:基于GCD,使用更加面向對象,線程生命周期系統(tǒng)自動管理,使用較多;

? 4)GCD:一套改進的C語言多線程API,能充分利用設備的多核優(yōu)勢,線程生命周期系統(tǒng)自動管理,使用最多;

二、pThread

? 純C語言API,使用較為麻煩。


圖1

三、NSThread

1、NSThread的創(chuàng)建

? 一個NSThread對象就代表一個線程,有三種方式創(chuàng)建:

? 1)創(chuàng)建線程后需要start啟動線程;

? 2)創(chuàng)建線程后自動啟動線程;

? 3)隱式創(chuàng)建并啟動線程;


圖2

運行程序,打印結果:


圖3

2、NSThread常用用法


圖4

3、NSThread的線程通信

在開發(fā)中,我們經常會在子線程進行耗時操作,操作結束后再回到主線程去刷新 UI。這就涉及到了子線程和主線程之間的通信,常用兩個API如下:


圖5

模擬子線程下載,主線程刷新UI代碼:


圖6

程序運行打印結果:


圖7

四、NSOperation

? NSOperation和NSOperationQueue是對GCD的一層封裝,NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類NSInvocationOperation和NSBlockOperation。

1、NSInvocationOperation

? NSInvocationOperation代碼:


圖8

2、NSBlockOperation

NSBlockOperation代碼:


圖9

3、NSOperationQueue

? NSOperation可以調用start方法來執(zhí)行任務,但默認是同步執(zhí)行的,如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統(tǒng)會自動異步執(zhí)行操作。

? 1)NSInvocationOperation與NSOperationQueue組合:


圖10

2)NSBlockOperation與NSOperationQueue組合:


圖11

運行結果:Thread1、Thread2、Thread3、Thread4都是子線程,所以添加到隊列之后,是異步的。

Thread4=<NSThread:0x6000020d9c80>{number=6,name=(null)}

Thread2 = <NSThread:0x6000020a3140>{number=5,name=(null)}

Thread3 = <NSThread:0x6000020dcec0>{number=7,name=(null)}

Thread1 = <NSThread:0x6000020bc740>{number=4,name=(null)}

4、控制NSOperationQueue是串行隊列還是并發(fā)隊列

可以通過設置maxConcurrentOperationCount的值來選擇串行隊列還是并發(fā)隊列。


圖12


圖13

5、NSOperation之間可以設置依賴來保證執(zhí)行順序

比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,代碼如下:


圖14


圖15

五、GCD

? Grand Central Dispatch (GCD)是Apple開發(fā)的一個多核編程的較新的解決方法,它主要用于優(yōu)化應用程序以支持多核處理器以及其他對稱多處理系統(tǒng),它是一個在線程池模式的基礎上執(zhí)行的并行任務,GCD是一個替代諸如NSThread等技術的很高效和強大的技術。

? GCD 會自動利用更多的 CPU 內核(比如雙核、四核),GCD 會自動管理線程的生命周期(創(chuàng)建線程、調度任務、銷毀線程),程序員只需要告訴 GCD 想要執(zhí)行什么任務,不需要編寫任何線程管理代碼。

? GCD兩個核心概念,任務和隊列,任務包括(同步執(zhí)行任務、異步執(zhí)行任務),隊列包括(串行隊列、并發(fā)隊列),隊列采用 FIFO(First In First Out)的原則,即先進先出原則。

1、同步執(zhí)行、異步執(zhí)行

? ? ? 同步執(zhí)行與異步執(zhí)行的區(qū)別:是否等待隊列的任務執(zhí)行結束,以及是否具備開啟新線程的能力。

? 1)同步執(zhí)行(sync):同步添加任務到指定的隊列中,在添加的任務執(zhí)行結束之前,會一直等待,直到隊列里面的任務完成之后再繼續(xù)執(zhí)行。只能在當前線程中執(zhí)行任務,不具備開啟新線程的能力。

? 2)異步執(zhí)行(async):異步添加任務到指定的隊列中,它不會做任何等待,可以繼續(xù)執(zhí)行任務。可以在新的線程中執(zhí)行任務,具備開啟新線程的能力。

? 注意:異步執(zhí)行(async)雖然具有開啟新線程的能力,但是并不一定開啟新線程。這跟任務所指定的隊列類型有關。

2、串行隊列、并發(fā)隊列

? 串行隊列和并發(fā)隊列的區(qū)別:執(zhí)行順序不同,以及開啟線程數不同。

? 1)串行隊列(Serial Dispatch Queue):讓任務一個接著一個地執(zhí)行(最多創(chuàng)建一個線程)。dispatch_get_main_queue() 主隊列就是一個串行隊列。

? 2)并發(fā)隊列(Concurrent Dispatch Queue):可以讓多個任務并發(fā)(同時)執(zhí)行(可以開啟多個線程)。dispatch_get_global_queue(0, 0) 全局隊列就是并發(fā)隊列。

? 注意:并發(fā)隊列的并發(fā)功能只有在異步(dispatch_async)方法下才有效。

3、經典各種組合模式

? 本來同步、異步、串行、并發(fā)有四種組合,但是當前代碼默認放在主隊列中,全局并發(fā)隊列可以作為普通并發(fā)隊列來使用,所以主隊列很有必要專門來研究一下,所以我們就有六種組合模式了。

? 1)同步執(zhí)行 + 串行隊列:沒有開啟新線程,串行執(zhí)行任務。


圖16

2)同步執(zhí)行 + 并發(fā)隊列:沒有開啟新線程,串行執(zhí)行任務。


圖17

?3)異步執(zhí)行 + 串行隊列:有開啟新線程(1條),串行執(zhí)行任務。


圖18

4)異步執(zhí)行 + 并發(fā)隊列:有開啟新線程(多條),并發(fā)執(zhí)行任務。


圖19

5)同步執(zhí)行+主隊列:死鎖卡住不執(zhí)行。其實如果在串行隊列中嵌套同步使用串行隊列,也會發(fā)生死鎖,原理和這個類似。所以項目中數據庫處理FMDataQueue嵌套使用時,需要注意死鎖問題。

? 如果不是在主線程執(zhí)行同步執(zhí)行+主隊列呢,那么不會發(fā)生死鎖(讀者可以自己代碼測試驗證)。


圖20

運行結果:程序死鎖崩潰,原因是默認viewDidLoad被添加到主隊列中(先運行完viewDidLoad,后運行添加的任務),然后又同步添加Thread1任務到主隊列中(先運行Thread1任務,后運行viewDidLoad任務),造成任務相互等待卡死,程序死鎖崩潰。

6)異步執(zhí)行+主隊列:沒有開啟新線程,串行執(zhí)行任務。


圖21

4、GCD線程間的通信

在項目開發(fā)中,一般耗時的操作在別的線程處理,然后在主線程刷新UI,GCD線程間的通信如下:


圖22

5、GCD其他的常見用法

? 1)柵欄方法(dispatch_barrier_async)

? 我們有時需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作,這就需要用到dispatch_barrier_async 方法在兩個操作組間形成柵欄。NSOperation的 addDependency 也是這個效果。


圖23

運行結果:先執(zhí)行第一組任務(Thread1、Thread2),然后再執(zhí)行柵欄的第二組任務(Thread3、Thread4、Thread5)。


圖24

2)延時方法(dispatch_after)

項目中我們有時需要幾秒之后再執(zhí)行某個方法,那么可以使用GCD的延時方法,而不用創(chuàng)建一個定時器來處理。


圖25

運行結果:先執(zhí)行Thread1,5s之后執(zhí)行Thread3,當前主隊列執(zhí)行完畢,然后執(zhí)行dispatch_after添加的任務Thread2。至于為什么Thread3、Thread2時間接近,而不是相差2s,因為執(zhí)行完Thread1的2s之后,Thread2添加到主隊列,正在等待主隊列執(zhí)行完畢。


圖26

3)執(zhí)行一次(dispatch_once)

? 項目開發(fā)中,我們經常使用單例模式,那么就對 dispatch_once 很熟悉,代碼只運行一次。并且即使在多線程的環(huán)境下,dispatch_once 也可以保證線程安全。


圖27

4)快速迭代方法(dispatch_apply)

? 通常我們會用 for 循環(huán)遍歷,但是 GCD 給我們提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次數將指定的任務追加到指定的隊列中,并等待全部任務執(zhí)行結束。如果是在串行隊列中使用dispatch_apply,那么就和 for 循環(huán)一樣,按順序同步執(zhí)行,所以實際使用的時候一般用并發(fā)隊列。


圖28

5)隊列組(dispatch_group)

? 有時候我們會有這樣的需求:分別異步執(zhí)行2個耗時任務,然后當2個耗時任務都執(zhí)行完畢后再回到主線程執(zhí)行任務,這時候我們可以用到 GCD 的隊列組。項目中常見使用場景有:請求多個接口數據返回后,再統(tǒng)一進行刷新;下載完多張圖片后,再統(tǒng)一進行合并繪制等。

? 調用隊列組的 dispatch_group_async 先把任務放到隊列中,然后將隊列放入隊列組中?;蛘呤褂藐犃薪M的 dispatch_group_enter、dispatch_group_leave 組合來實現 dispatch_group_async。

? 調用隊列組的 dispatch_group_notify 回到指定線程執(zhí)行任務。或者使用 dispatch_group_wait 回到當前線程繼續(xù)向下執(zhí)行(會阻塞當前線程)。


圖29


圖30

運行結果:先異步執(zhí)行group任務(Thread1、Thread2、Thread3),group執(zhí)行完畢后,再執(zhí)行nofity的任務。


圖31

6)信號量(dispatch_semaphore)

? 項目開發(fā)中,有時候有這樣的需求:異步執(zhí)行耗時任務,并使用異步執(zhí)行的結果進行一些額外的操作。比如說:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通過引入信號量的方式,等待異步執(zhí)行任務結果,獲取到 tasks,然后再返回該 tasks。

? GCD 中的信號量是指Dispatch Semaphore,是持有計數的信號。在Dispatch Semaphore中,使用計數來完成這個功能,計數小于 0 時等待,不可通過。計數為 0 或大于 0 時,不等待,可通過。信號量主要用于:

? a、保持線程同步,將異步執(zhí)行任務轉換為同步執(zhí)行任務;

? b、保證線程安全,為線程加鎖;


圖32

我們自己用信號量實現線程同步


圖33

運行結果:semaphore初始創(chuàng)建時計數為 0,異步將Thread1任務添加到全局并發(fā)隊列,不做等待,接著執(zhí)行 dispatch_semaphore_wait 方法,semaphore 減 1,此時 semaphore == -1,當前線程進入等待狀態(tài)。

? 然后,異步任務 1 開始執(zhí)行。任務 1 執(zhí)行到 dispatch_semaphore_signal 之后,總信號量加 1,此時 semaphore == 0,正在被阻塞的線程(主線程)恢復繼續(xù)執(zhí)行,最后打印end。


圖34

我們自己用信號量實現?semaphore 加鎖


圖35


圖36

運行結果:ticketCount 按照順序依次遞減1,“火車票售完”打印三次是因為有三條線程運行。

? 分析過程:首先三條線程都并發(fā)執(zhí)行sellTickets方法,最快的一條線程首先執(zhí)行dispatch_semaphore_wait,信號量減一,此時信號量為0,該條線程繼續(xù)執(zhí)行wait之后的代碼,而其他較慢的兩條線程進行等待新的信號量出現,較快的一條線程賣票之后,發(fā)送一個信號量,那么較慢的兩條線程其中的一條執(zhí)行wait之后的代碼,如此循環(huán),保證一個時間點只有一條線程進行賣票,從而保證線程安全。


圖37

如果把信號量的代碼注釋,運行結果:剩余票數順序明顯不對,數據錯亂。


圖38

六、線程安全

? 當多個線程訪問同一塊資源時,很容易引發(fā)數據錯亂和數據安全問題。線程安全問題:一般使用線程同步技術,同步技術有加鎖、串行隊列、信號量等。

? 串行隊列,上文已經有介紹,比如項目中FMDBQueue,就是在串行隊列中同步操作數據庫,從而保證線程安全。

? 信號量,上文已經有介紹,其實類似于互斥鎖。

? 下面主要講iOS中常見的鎖:

? 從大的方向講有兩種鎖:自旋鎖、互斥鎖。

自旋鎖:線程反復檢查鎖變量是否可用,是一種忙等狀態(tài),自旋鎖避免了進程上下文的調度開銷,因此對于線程只會阻塞很短時間的場合是有效的。自旋鎖的性能高于互斥鎖,因為響應速度快,但是可能發(fā)生優(yōu)先級反轉問題(如果等待鎖的線程優(yōu)先級較高,它會一直占用著CPU資源,優(yōu)先級低的線程就無法釋放鎖)。常見的自旋鎖包括:OSSpinLock、atomic等。

互斥鎖:如果共享數據已經有其他線程加鎖了,線程會進入休眠狀態(tài)等待鎖,因此適用于線程阻塞時間較長的場合。常見的互斥鎖包括:os_unfair_lock、pthread_mutex、dispatch_semaphore、@synchronized,其中pthread_mutex又衍生出NSLock、NSCondition、NSConditionLock、NSRecursiveLock,更加面向對象的鎖。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 一、 進程、線程、多線程 什么是進程 進程是指系統(tǒng)中正在運行的一個應用程序 每個進程之間是獨立的,每個進程均運行在...
    小猴子兵兵兵閱讀 1,951評論 0 0
  • 類型簡介實現語言線程生命周期pthreadposix接口,適合跨平臺開發(fā),使用難度較大c手動管理NSThread面...
    FengyunSky閱讀 360評論 0 0
  • 用了這么久的GCD, 不總結一下實在良心上過不去. 有那么點白那啥的意思. 廢話不多說. 走你 ? 1 GCD介紹...
    lb_閱讀 1,064評論 2 4
  • iOS中的常見多線程方案 GCD的常用函數 GCD中有2個用來執(zhí)行任務的函數 用同步的方式執(zhí)行任務dispatch...
    斑駁的流年無法釋懷閱讀 370評論 0 2
  • 進程與線程 進程就是一個應用程序在處理機上的一次執(zhí)行過程,它是一個動態(tài)的概念,而線程是進程中的一部分,進程包含多個...
    朱敏_ITer閱讀 415評論 0 1

友情鏈接更多精彩內容