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