IOS 多執行緒技術

清風颺發表於2016-05-23

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> 獲取執行緒資訊

?
1
[NSThread currentThread]

    4> 執行緒休眠

?
1
[NSThread sleepForTimeInterval:2.0f];

特點:

    1> 使用簡單,輕量級;

    2> 不能控制執行緒的數量以及執行順序.

NSObject的多執行緒方法注意事項:

    1> NSObject的多執行緒方法使用的是NSThread的多執行緒技術.

    2> NSThread的多執行緒技術不會自動使用@autoreleasepool.

在使用NSObject或NSThread的多執行緒技術時,如果涉及到物件分配,需要手動新增@autoreleasepool.

相關文章