iOS多線程總結

iOS多線程總結

一、iOS多線程對比

1.NSThread
每一個NSThread對象對應一個線程,真正最原始的線程。
1)優勢:NSThread 輕量級最低,相對簡單。
2)缺點:手動管理全部的線程活動,如生命週期、線程同步、睡眠等。編程

2.NSOperation
自帶線程管理的抽象類。
1)優勢:自帶線程週期管理,操做上可更注重本身邏輯。
2)缺點:面向對象的抽象類,只能實現它或者使用它定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation。後端

3.GCD
Grand Central Dispatch (GCD)是Apple開發的一個多核編程的解決方法。
1)優勢:最高效,避開併發陷阱。
2)缺點:基於C實現。api

4.選擇小結
1)簡單而安全的選擇NSOperation實現多線程便可。
2)處理大量併發數據,又追求性能效率的選擇GCD。
3) 在頻繁使用多線程的程序中通常不建議使用NSThread安全

二、NSThread

NSThread是Objective-C 中提供的對POSIX 線程 API的封裝。服務器

直接使用線程可能會引起的一個問題是,若是你的代碼和所基於的框架代碼都建立本身的線程時,那麼活動的線程數量有可能以指數級增加。這在大型工程中是一個常見問題。例如,在 8 核 CPU 中,你建立了 8 個線程來徹底發揮 CPU 性能。然而在這些線程中你的代碼所調用的框架代碼也作了一樣事情(由於它並不知道你已經建立的這些線程),這樣會很快產生成成百上千的線程。代碼的每一個部分自身都沒有問題,然而最後卻仍是致使了問題。使用線程並非沒有代價的,每一個線程都會消耗一些內存和內核資源。網絡

2.一、三種實現開啓線程方式

動態實例化

NSThread *thread = [[NSThread alloc] initWithTarget:self            selector:@selector(loadImageSource:) object:imgUrl];

thread.threadPriority = 1;// 設置線程的優先級(0.0 - 1.0,1.0最高級)
[thread start];

靜態實例化

[NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl];

隱式實例化

[self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];

//在指定線程上操做
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];

自定義

須要繼承NSThread,而且重寫main方法,而後經過start方法啓動。多線程

2.二、常見操做方式

//取消線程
- (void)cancel;

//啓動線程
- (void)start;

//判斷某個線程的狀態的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//設置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//獲取當前線程信息
+ (NSThread *)currentThread;

//獲取主線程信息
+ (NSThread *)mainThread;

//使當前線程暫停一段時間,或者暫停到某個時刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

三、GCD(Grand Centra Dispatch)

經過 GCD,開發者不用再直接跟線程打交道了,只須要向隊列中添加代碼塊便可,GCD 在後端管理着一個線程池。GCD 不只決定着你的代碼塊將在哪一個線程被執行,它還根據可用的系統資源對這些線程進行管理。這樣能夠將開發者從線程管理的工做中解放出來,經過集中的管理線程,來緩解大量線程被建立的問題。併發

GCD 帶來的另外一個重要改變是,做爲開發者能夠將工做考慮爲一個隊列,而不是一堆線程,這種並行的抽象模型更容易掌握和使用。app

GCD 公開有 5 個不一樣的隊列:運行在主線程中的 main queue,3 個不一樣優先級的後臺隊列,以及一個優先級更低的後臺隊列(用於 I/O)。 另外,開發者能夠建立自定義隊列:串行或者並行隊列。自定義隊列很是強大,在自定義隊列中被調度的全部 block 最終都將被放入到系統的全局隊列中和線程池中。框架

<center>image_1ap71r5b01bfa96hrgk5971noa9.png-39kB</center>

在絕大多數狀況下使用默認的優先級隊列就能夠了。若是執行的任務須要訪問一些共享的資源,那麼在不一樣優先級的隊列中調度這些任務很快就會形成不可預期的行爲。這樣可能會引發程序的徹底掛起,由於低優先級的任務阻塞了高優先級任務,使它不能被執行。

3.一、三種線程隊列類型

主線程隊列 main queue

它是全局可用的serial queue,它是在應用程序主線程上執行任務的。

dispatch_get_main_queue()

並行隊列global dispatch queue

又稱爲global dispatch queue,能夠併發地執行多個任務,可是執行完成的順序是隨機的。

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

這裏的兩個參數得說明一下:第一個參數用於指定優先級,分別使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW兩個常量來獲取高和低優先級的兩個queue;第二個參數目前未使用到,默認0便可。

串行隊列serial queues

又稱爲private dispatch queues,同時只執行一個任務。Serial queue一般用於同步訪問特定的資源或數據。當你建立多個Serial queue時,雖然它們各自是同步執行的,但Serial queue與Serial queue之間是併發執行的。

dispatch_queue_create("minggo.app.com", NULL);

凡是本身建立的隊列默認爲串行隊列。可在建立參數上添加DISPATCH_QUEUE_CONCURRENT,構建並行隊列。

//串行隊列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
  //並行隊列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

3.二、兩種線程類型

同步線程

dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

異步線程

dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

3.三、6種多線程實現

後臺執行線程建立

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self loadImageSource:imgUrl1];
});

UI線程執行(只是爲了測試,長時間加載內容不放在主線程)

dispatch_async(dispatch_get_main_queue(), ^{
    [self loadImageSource:imgUrl1];
});

一次性執行(經常使用來寫單例)

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    [self loadImageSource:imgUrl1];
});

併發地執行循環迭代

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count = 10;
dispatch_apply(count, queue, ^(size_t i) {
    NSLog(@"循環執行第%li次",i);
    [self loadImageSource:imgUrl1];
});

延遲執行

double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self loadImageSource:imgUrl1];
});

自定義dispatch_queue_t

dispatch_queue_t urls_queue = dispatch_queue_create("minggo.app.com", NULL);
dispatch_async(urls_queue, ^{
    [self loadImageSource:imgUrl1];
});

3.四、隊列組(dispatch_group_t)

隊列組能夠將不少隊列添加到一個組裏,這樣作的好處是,當這個組裏全部的任務都執行完了,隊列組會經過一個方法通知咱們。

dispatch_group_async能夠實現監聽一組任務是否完成,完成後獲得通知執行其餘的操做。這個方法頗有用,好比你執行三個下載任務,當三個任務都下載完成後你才通知界面說完成的了。下面是一段例子代碼

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group1");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group2");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"group3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"updateUi");
    });
    dispatch_release(group);

3.五、其餘用法

dispatch_barrier_async的使用

dispatch_barrier_async是在前面的任務執行結束後它才執行,並且它後面的任務等它執行完成以後纔會執行。

dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);  
dispatch_async(queue, ^{  
    [NSThread sleepForTimeInterval:2];  
    NSLog(@"dispatch_async1");  
});  
dispatch_async(queue, ^{  
    [NSThread sleepForTimeInterval:4];  
    NSLog(@"dispatch_async2");  
});  
dispatch_barrier_async(queue, ^{  
    NSLog(@"dispatch_barrier_async");  
    [NSThread sleepForTimeInterval:4];  
  
});  
dispatch_async(queue, ^{  
    [NSThread sleepForTimeInterval:1];  
    NSLog(@"dispatch_async3");  
});

打印結果:
2012-09-25 16:20:33.967 gcdTest[45547:11203] dispatch_async1
2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_async2
2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async
2012-09-25 16:20:40.970 gcdTest[45547:11303] dispatch_async3

dispatch_apply的使用

執行某個代碼片斷N次。

dispatch_apply(5, globalQ, ^(size_t index) {
    // 執行5次
});

四、NSOperation

NSOperation 是蘋果公司對 GCD 的封裝,徹底面向對象,NSOperationNSOperationQueue 分別對應 GCD 的任務和隊列 。操做步驟也很好理解:

  1. 將要執行的任務封裝到一個 NSOperation 對象中。

  2. 將此任務添加到一個 NSOperationQueue 對象中。

4.一、NSOperation類型

NSOperation 只是一個抽象類,因此不能封裝任務。但它有 2 個子類用於封裝任務。分別是:NSInvocationOperationNSBlockOperation 。建立一個 Operation 後,須要調用 start 方法來啓動任務,它會 默認在當前隊列同步執行。固然你也能夠在中途取消一個任務,只須要調用其 cancel方法便可。

//1.建立NSInvocationOperation對象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

 //2.開始執行
 [operation start];
  
  //1.建立NSBlockOperation對象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];

  //2.開始任務
  [operation start];

自定義Operation

除了上面的兩種 Operation 之外,咱們還能夠自定義 Operation。自定義 Operation 須要繼承 NSOperation 類,並實現其 main() 方法,由於在調用 start() 方法的時候,內部會調用 main() 方法完成相關邏輯。因此若是以上的兩個類沒法知足你的慾望的時候,你就須要自定義了。你想要實現什麼功能均可以寫在裏面。除此以外,你還須要實現 cancel() 在內的各類方法。

若是是須要併發執行的話,還必需要重寫
start

asynchronous

executing

finished
方法。

4.二、兩種隊列(NSOperation)

NSOperationQueue 有兩種不一樣類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上,而自定義隊列在後臺執行。在兩種類型中,這些隊列所處理的任務都使用 NSOperation 的子類來表述。

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];  //主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //自定義隊列

//添加一個NSOperation
[queue addOperation:operation]

//添加一組NSOperation
[queue addOperations:operations waitUntilFinished:NO

//添加一個block形式的Operation
[queue addOperationWithBlock:^{
    //執行一個Block的操做        
}];

[queue setMaxConcurrentOperationCount:1];

//單個NSOperation取消
[operation cancel]

//取消NSOperationQueue中的全部操做
[queue cancelAllOperations]

// 暫停queue  
[queue setSuspended:YES];  
  
// 繼續queue  
[queue setSuspended:NO];

咱們能夠經過設置maxConcurrentOperationCount 屬性來控制併發任務的數量,當設置爲 1 時, 那麼它就是一個串行隊列。主對列默認是串行隊列,這一點和 dispatch_queue_t是類似的。

NSOperation 有一個很是實用的功能,那就是添加依賴。好比有 3 個任務:A: 從服務器上下載一張圖片,B:給這張圖片加個水印,C:把圖片返回給服務器。這時就能夠用到依賴了:

//1.任務一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下載圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//2.任務二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任務三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.設置依賴
[operation2 addDependency:operation1];      //任務二依賴任務一
[operation3 addDependency:operation2];      //任務三依賴任務二

//5.建立隊列並加入任務
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

五、使用選擇

目前在 iOS 和 OS X 中有兩套先進的同步 API 可供咱們使用:NSOperation 和 GCD 。其中 GCD 是基於 C 的底層的 API ,而 NSOperation 則是 GCD 實現的 Objective-C API。 雖然 NSOperation 是基於 GCD 實現的, 可是並不意味着它是一個 GCD 的 「dumbed-down」 版本, 相反,咱們能夠用NSOperation 輕易的實現一些 GCD 要寫大量代碼的事情。 所以, NSOperationQueue 是被推薦使用的, 除非你遇到了 NSOperationQueue 不能實現的問題。

爲何優先使用NSOperationQueue而不是GCD

曾經我有一段時間我很是喜歡使用GCD來進行併發編程,由於雖然它是C的api,可是使用起來卻很是簡單和方便, 不過這樣也就容易使開發者忘記併發編程中的許多注意事項和陷阱。
好比你可能寫過相似這樣的代碼(這樣來請求網絡數據):

dispatch_async(_Queue, ^{
  
//請求數據
NSData *data = [NSData dataWithContentURL:[NSURL URLWithString:@"http://domain.com/a.png"]];

    dispatch_async(dispatch_get_main_queue(), ^{

         [self refreshViews:data];
    });
});

沒錯,它是能夠正常的工做,可是有個致命的問題:這個任務是沒法取消的 dataWithContentURL:是同步的拉取數據,它會一直阻塞線程直到完成請求,若是是遇到了超時的狀況,它在這個時間內會一直佔有這個線程;在這個期間併發隊列就須要爲其餘任務新建線程,這樣可能致使性能降低等問題。
所以咱們不推薦這種寫法來從網絡拉取數據。

操做隊列(operation queue)是由 GCD 提供的一個隊列模型的 Cocoa 抽象。GCD 提供了更加底層的控制,而操做隊列則在 GCD 之上實現了一些方便的功能,這些功能對於 app 的開發者來講一般是最好最安全的選擇。NSOperationQueue相對於GCD來講有如下優勢:

  • 提供了在 GCD 中不那麼容易複製的有用特性。

  • 能夠很方便的取消一個NSOperation的執行

  • 能夠更容易的添加任務的依賴關係

  • 提供了任務的狀態:isExecuteing, isFinished.


參考文章
一、談iOS多線程(NSThread、NSOperation、GCD)編程
二、併發編程:API 及挑戰
三、iOS多線程編程之Grand Central Dispatch(GCD)介紹和使用
四、Cocoa深刻學習:NSOperationQueue、NSRunLoop和線程安全

相關文章
相關標籤/搜索