之前講過多線程之NSOperation,今天來講講代碼更加簡潔和高效的GCD。下面說的內容都是基于iOS6以后和ARC下。
Grand Central Dispatch (GCD)簡介
Grand Central Dispatch(GCD) 是異步執(zhí)行任務的技術之一。開發(fā)者只需要定義想執(zhí)行的任務并追加到適當的Dispatch Queue中,GCD就能生成必要的線程并計劃執(zhí)行任務。由于線程管理是作為系統(tǒng)的一部分來實現的,因此可以統(tǒng)一管理,也可執(zhí)行任務,這樣就比以前的線程更有效率。GCD用非常簡潔的代碼,就可以實現多線程編程。
這篇主要講Dispatch Queue的一些基本東西,后續(xù)會加入其他相關內容的介紹。
Dispatch Queue 種類
Dispatch Queue是執(zhí)行處理的等待隊列,通過調用dispatch_async等函數,以block的形式將任務追加到Dispatch Queue中。Dispatch Queue按照添加進來的順序(FIFO)執(zhí)行任務處理。但是在任務執(zhí)行處理方式上,分為Serial Dispatch Queue和Concurrent Dispatch Queue。兩者的區(qū)別如表格所示
| Dispatch Queue分類 | 說明 |
|---|---|
| Serial Dispatch Queue | 串行的隊列,每次只能執(zhí)行一個任務,并且必須等待前一個執(zhí)行任務完成 |
| Concurrent Dispatch Queue | 一次可以并發(fā)執(zhí)行多個任務,不必等待執(zhí)行中的任務完成 |
下面用代碼來演示下:
dispatch_async(queue, ^{
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
dispatch_async(queue, ^{
NSLog(@"3");
});
dispatch_async(queue, ^{
NSLog(@"4");
});
如果上面的queue是Serial Dispatch Queue的話,那么輸出的結果一定是1,2,3,4。因為執(zhí)行順序是確定的,并且后續(xù)的任務必須在之前的任務執(zhí)行完成后才能執(zhí)行。
如果是Concurrent Dispatch Queue的話,那么輸出的結果就不一定是1,2,3,4了。因為這些任務都是并發(fā)執(zhí)行,并且不需要等待執(zhí)行中的任務完成,如果其中任意一個任務完成將立即執(zhí)行后面的任務。
Dispatch Queue 創(chuàng)建
在自定義創(chuàng)建前,我們先看看系統(tǒng)為我們提供的幾個全局的Dispatch Queue:
| 名稱 | Dispatch Queue 的種類 | 說明 |
|---|---|---|
| Main Dispatch Queue | Serial Dispatch Queue | 主線程執(zhí)行 |
| Global Dispatch Queue (HIGH) | Concurrent Dispatch Queue | 執(zhí)行優(yōu)先級:高 |
| Global Dispatch Queue (DEFAULT) | Concurrent Dispatch Queue | 執(zhí)行優(yōu)先級:默認 |
| Global Dispatch Queue (LOW) | Concurrent Dispatch Queue | 執(zhí)行優(yōu)先級:低 |
| Global Dispatch Queue (BACKGROUND) | Concurrent Dispatch Queue | 執(zhí)行優(yōu)先級:后臺 |
從表格中我們可以知道我們的主線程就是Serial Dispatch Queue,而之后的三種Dispatch Queue 則是Concurrent Dispatch Queue。這也是為什么我們不能把耗時的任務放在主線程里面去操作。
如果沒有特殊需求,我們可以直接獲取這些queue來執(zhí)行我們的任務:
//主線程
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//HIGH
dispatch_queue_t highQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//DEFAULT
dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//LOW
dispatch_queue_t lowQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//BACKGROUND
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
下面看看如何自定義一個queue:
//串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
//并發(fā)隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
通過調用dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)這個函數。第一個參數是給這個queue起的標識,這個在調試的可以看到是哪個隊列在執(zhí)行,或者在crash日志中,也能做為提示。第二個是需要創(chuàng)建的隊列類型,是串行的還是并發(fā)的。當然你也可以通過dispatch_queue_get_label(dispatch_queue_t queue)獲取你創(chuàng)建queue的名字。
使用
- 異步執(zhí)行
這個也是我們使用最多的地方,我們直接調用dispatch_async這個函數,就可以將我們要追加的任務添加到隊列里面,并立即返回,異步的執(zhí)行。這個不多講。
dispatch_async(queue, ^{
NSLog(@"1");
});
- 同步執(zhí)行
這點我們可能用得不是很多,但是一用不好就出現問題了。當調用這個dispatch_sync函數的時候,這個線程將不會立即返回,直到這個線程執(zhí)行完畢??聪孪旅娴拇a:
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"2");
});
如果你在主線程里面調用這個函數,那么,很遺憾,主線程將被卡主3秒鐘。當主線程調用這個方法的時候,由于是同步,不會立即返回,直到這個里面內容執(zhí)行完畢才能返回。這個時候不管你這個queue是什么類型,都一樣。既然不能立即返回,我們可以在一個異步執(zhí)行的線程中,再去調用這個同步方法。
dispatch_async(serialQueue, ^{
NSLog(@"4");
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"5");
});
NSLog(@"6");
});
那這個時候,調用這個方法的時候這個線程將立即返回。而同步在這個線程里面執(zhí)行。這個時候將輸出,4,5,6的結果。
同步執(zhí)行的死鎖問題
現在把上面代碼拿出來改下
dispatch_async(serialQueue, ^{
NSLog(@"4");
dispatch_sync(serialQueue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"5");
});
NSLog(@"6");
});
這個時候,我把queue的類型設置為串行的類型。這個時候將只會輸出4。為什么呢?系統(tǒng)調用這個線程的時候,首先輸出4,然后繼續(xù)執(zhí)行這個里面的同步線程。由于我的這個queue是串行的,也就是后續(xù)的任務必須在之前的執(zhí)行中任務完成后才能繼續(xù)執(zhí)行。但是這個同步執(zhí)行的線程不會立即返回,必須等到它執(zhí)行完成才能返回。這樣最外面的任務沒法執(zhí)行完,而里面的同步線程又不能立即返回,所以就形成了死鎖。
因此在你的編程中使用這個API的時候,一定要當心,不然就會形成死鎖。