iOS中的多執行緒
首先來了解什麼是多執行緒,程式和執行緒的區別.
程式:
正在進行中的程式被稱為程式,負責程式執行的記憶體分配;
每一個程式都有自己獨立的虛擬記憶體空間.
執行緒:(主執行緒最大佔1M的棧區空間,每條子執行緒最大佔512K的棧區空間)
執行緒是程式中一個獨立的執行路徑(控制單元);
一個程式中至少包含一條執行緒,即主執行緒;
可以將耗時的執行路徑(如網路請求)放在其他執行緒中執行;
執行緒不能被殺掉,但是可以暫停/休眠一條執行緒.
建立執行緒的目的:
開啟一條新的執行路徑,執行指定的程式碼,與主執行緒中的程式碼實現同時執行.
多工排程系統:
每個應用程式由作業系統分配的短暫的時間片(Timeslice)輪流使用CPU,由於CPU對每個時間片的處理速度非常快,因此,使用者看來這些任務好像是同時執行的.
併發:
指兩個或多個任務在同一時間間隔內發生,但是,在任意一個時間點上,CPU只會處理一個任務.
多執行緒的優勢:
1> 充分發揮多核處理器優勢,將不同執行緒任務分配給不同的處理器,真正進入"並行運算"狀態;
2> 將耗時的任務分配到其他執行緒執行,由主執行緒負責統一更新介面會使應用程式更加流暢,使用者體驗更好;
3> 當硬體處理器的數量增加,程式會執行更快,而程式無需做任何調整.
弊端:
新建執行緒會消耗記憶體空間和CPU時間,執行緒太多會降低系統的執行效能.
iOS的三種多執行緒技術特點:
1.NSThread:
1> 使用NSThread物件建立一個執行緒非常方便;
2> 但是!要使用NSThread管理多個執行緒非常困難,不推薦使用;
3> 技巧!使用[NSThread currentThread]跟蹤任務所線上程,適用於這三種技術.
2.NSOperation/NSOperationQueue:
1> 是使用GCD實現的一套Objective-C的API;
2> 是物件導向的多執行緒技術;
3> 提供了一些在GCD中不容易實現的特性,如:限制最大併發數量,操作之間的依賴關係.
3.GCD---Grand Central Dispatch:
1> 是基於C語言的底層API;
2> 用Block定義任務,使用起來非常靈活便捷;
3> 提供了更多的控制能力以及操作佇列中所不能使用的底層函式.
iOS的開發者需要了解三種多執行緒技術的基本使用,因為在實際開發中會根據實際情況選擇不同的多執行緒技術.
GCD基本思想
GCD的基本思想就是將操作S放在佇列S中去執行.
1> 操作使用Blocks定義;
2> 佇列負責排程任務執行所在的執行緒以及具體的執行時間;
3> 佇列的特點是先進先出(FIFO)的,新新增至佇列的操作都會排在隊尾.
提示:
GCD的函式都是以dispatch(分派/排程)開頭的.
佇列:
dispatch_queue_t
序列佇列: 佇列中的任務只會順序執行;
並行佇列: 佇列中的任務通常會併發執行.
操作:
dispatch_async 非同步操作,會併發執行,無法確定任務的執行順序;
dispatch_sync 同步操作,會依次順序執行,能夠決定任務的執行順序.
佇列不是執行緒,也不表示對應的CPU.佇列就是負責排程的.多執行緒技術的目的,就是為了在一個CPU上實現快速切換!
在序列佇列中:
同步操作不會新建執行緒,操作順序執行(沒用!);
非同步操作會新建執行緒,操作順序執行(非常有用!) (應用場景:既不影響主執行緒,又需要順序執行的操作).
在並行佇列中:
同步操作不會新建執行緒,操作順序執行;
非同步操作會新建多個執行緒,操作無序執行(有用,容易出錯),佇列前如果有其他任務,會等待前面的任務完成之後再執行.應用場景:既不影響主執行緒,又不需要順序執行的操作.
全域性佇列:
全域性佇列是系統的,直接拿過來(GET)用就可以,與並行對立類似,但除錯時,無法確認操作所在佇列.
主佇列:
每一個應用程式都對應唯一一個主佇列,直接GET即可,在多執行緒開發中,使用主佇列更新UI;
注意:
主佇列中的操作都應該在主執行緒上順序執行,不存在非同步的概念.
如果把主執行緒中的操作看作是一個大的Block,那麼除非主執行緒被使用者殺掉,否則永遠不會結束.所以主佇列中新增的同步操作永遠不會被執行,會死鎖.
不同佇列中巢狀同步操作dispatch_sync的結果:
1
2
3
4
5
6
7
8
|
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t q = dispatch_queue_create( "m.baidu.com" , DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t q = dispatch_queue_create( "m.baidu.com" , DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q = dispatch_get_main_queue();
|
同步操作dispatch_sync的應用場景:
阻塞並行佇列的執行,要求某一操作執行後再進行後續操作,如使用者登入.
確保塊程式碼之外的區域性變數確實被修改.
[NSThread sleepForTimeInterval:2.0f] 通常在多執行緒除錯中用於模擬耗時操作,在釋出的應用程式中,不要使用此方法!
無論什麼佇列和什麼任務,執行緒的建立和回收都不需要程式設計師參與.執行緒的建立回收工作是由佇列負責的.
GCD優點:
1> 通過GCD,開發者不用再直接跟執行緒打交道,只需要向佇列中新增程式碼塊即可.
2> GCD在後端管理著一個執行緒池,GCD不僅決定著程式碼塊將在哪個執行緒被執行,它還根據可用的系統資源對這些執行緒進行管理,從而讓開發者從執行緒管理的工作中解放出來;通過集中的管理執行緒,緩解大量執行緒被建立的問題.
3> 使用GCD,開發者可以將工作考慮為一個佇列,而不是一堆執行緒,這種並行的抽象模型更容易掌握和使用.
GCD佇列:
蘋果官方給出的GCD佇列示意圖:
從中可以看出: GCD公開有5個不同的佇列:執行在主執行緒中的主佇列,3個不同優先順序的後臺佇列以及一個優先順序更低的後臺佇列(用於I/O).
自定義佇列:序列和並行佇列.自定義佇列非常強大,建議在開發中使用.
在自定義佇列中被排程的所有Block最終都將被放入到系統的全域性佇列中和執行緒池中.
提示:
不建議使用不同優先順序的佇列,因為如果設計不當,可能會出現優先順序反轉,即低優先順序的操作阻塞高優先順序的操作.
NSOperation&NSOperationQueue
簡介:
1> NSOperationQueue(操作佇列)是由GCD提供的佇列模型的Cocoa抽象,是一套Objective-C的API;
2> GCD提供了更加底層的控制,而NSOperationQueue(操作佇列)則在GCD之上實現了一些方便的功能,這些功能對開發者而言通常是最好最安全的選擇.
佇列及操作:
NSOperationQueue有兩種不同型別的佇列:主佇列和自定義佇列.
主佇列執行在主執行緒上,自定義佇列在後臺執行.
佇列處理的任務是NSOperation的子類:NSInvocationOperation 和 NSBlockOperation.
NSOperation的基本使用步驟:
定義操作佇列 --> 定義操作 -->將操作新增到佇列.
提示:
一旦將操作新增到佇列,操作就會立即被排程執行.
NSInvocationOperation(排程操作)
1> 定義佇列:
1
|
self.myQueue = [[NSOpertaionQueue alloc] init];
|
2> 操作呼叫的方法:
1
2
3
4
|
-( void )operationAction:(id)obj
{
NSLog(@ "%@----obj : %@ " ,[NSThread currentThread], obj);
};
|
3> 定義操作並新增到佇列:
1
2
3
|
NSInvocationOperation *op = [[NSInvocationOperation alloc]
initWithTarget:self selector:@selector(operationAction:) object:@(i)];
[self.myQueue addOperation:op]
|
提示:需要準備一個被排程的方法,並且能夠接收一個引數.
NSBlockOperation(塊操作)
定義操作並新增到佇列:
1
2
3
4
|
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
[self operationAction:@ "Block Operation" ];
}];
[self.myQueue addOperation:op];
|
NSBlockOperation比NSInvocationOperation更加靈活;
設定操作的依賴關係:
利用 " addDependency "可以指定操作之間彼此的依賴關係(執行先後順序),但是注意不要出現迴圈依賴.
設定同時併發的執行緒數量:
1
|
[self.myQueue setMaxConcurrentOperationCount:2];
|
NSOperation小結:
從本質上看,操作佇列的效能會比GCD略低,不過,大多數情況下這點負面影響可以忽略不計.操作佇列是併發程式設計的首選工具.
在這裡,推薦一個非常好用的第三方程式設計框架AFN,底層用GCD開發,開發的介面是NSOperation的.
多執行緒中得迴圈引用問題:
如果self物件持有操作物件的引用,同時操作物件當中又直接訪問了self時,才會造成迴圈引用.
單純在操作物件中使用self不會造成迴圈引用.
注意: 此時不要使用[weakSelf].
多執行緒中的資源共享問題:
併發程式設計中許多問題的根源就是在多執行緒中訪問共享資源.資源可以是一個屬性,一個物件,網路裝置或者一個檔案等.
在多執行緒中任何一個共享的資源都可能是一個潛在的衝突點,必須精心設計以防止這種衝突的發生.
為了保證效能,atomic僅針對屬性的setter方法做了保護.
爭搶共享資源時,如果涉及到屬性的getter方法,可以使用互斥鎖(@synchronized)可以保證屬性在多個執行緒之間的讀寫都是安全的.
無論是atomic還是@synchronized ,使用的代價都是高昂的.
建議:
多執行緒是併發執行多個任務提高效率的,如果可能,應該線上程中避免爭搶共享資源.
正是出於效能的考慮,UIKit中的絕大多數類都不是執行緒安全的,因此,蘋果公司要求:更新UI相關的操作,應該在主執行緒中執行.
NSObject的多執行緒方法
1> 開啟後臺執行任務的方法:
1
|
- ( void )performSelectorInBackground:(SEL)@Selector withObject:(id)arg
|
2> 在後臺執行緒中通知主執行緒執行任務的方法:
1
|
- ( void )performSelectorOnMainThread:(SEL)@Selector withObject:(id)arg waitUntilDone:( BOOL )wait
|
3> 獲取執行緒資訊
4> 執行緒休眠
1
|
[NSThread sleepForTimeInterval:2.0f];
|
特點:
1> 使用簡單,輕量級;
2> 不能控制執行緒的數量以及執行順序.
NSObject的多執行緒方法注意事項:
1> NSObject的多執行緒方法使用的是NSThread的多執行緒技術.
2> NSThread的多執行緒技術不會自動使用@autoreleasepool.
在使用NSObject或NSThread的多執行緒技術時,如果涉及到物件分配,需要手動新增@autoreleasepool.