GCD是iOS開發(fā)中常用的多線程工具,它是一套低層級(jí)的C API,通
過 GCD,開發(fā)者只需要向隊(duì)列中添加一段代碼塊(block或C函數(shù)指針),而不需要直接和線程打交道。GCD也可以說是面向隊(duì)列編程,將我們希望添加的同步、異步任務(wù)放到不同的隊(duì)列中去執(zhí)行。
本文目錄
1、常見概念解析
2、同步異步函數(shù)使用方法探究
3、死鎖發(fā)生的場(chǎng)景
4、常用API總結(jié)
一、常見概念解析
GCD的使用必定繞不開多線程,先來說一說多線程中的一些概念,可以幫助童鞋們更好的理解后面的內(nèi)容,新手也可以借此掃盲
Dispatch Queue:執(zhí)行處理的等待隊(duì)列,開發(fā)者將block添加到隊(duì)列當(dāng)中,不需要關(guān)心線程的創(chuàng)建等過程(任務(wù)添加基于FIFO原則)。隊(duì)列分為兩種,一種是Serial Dispatch Queue(串行隊(duì)列),另一種是Concurrent Dispatch Queue(并發(fā)隊(duì)列)。
Serial Dispatch Queue:一次只能執(zhí)行一個(gè)任務(wù),后面的任務(wù)必須等待當(dāng)前任務(wù)執(zhí)行完畢才能開始執(zhí)行
Concurrent Dispatch Queue:一次可以執(zhí)行多個(gè)任務(wù),后面的任務(wù)不需要等待當(dāng)前的任務(wù)執(zhí)行完畢
同步:同步任務(wù)創(chuàng)建后必須立即執(zhí)行,并且堵塞當(dāng)前線程,當(dāng)該任務(wù)返回后線程繼續(xù)處理后面的任務(wù)
異步:異步任務(wù)創(chuàng)建后立即返回,不會(huì)堵塞線程,后面的任務(wù)不必等待該任務(wù)返回,一般會(huì)新創(chuàng)建一個(gè)線程來處理這個(gè)任務(wù)
二、同步異步函數(shù)使用方法探究
只通過我的敘述可能還是有些迷惑,我們通過代碼來幫助理解概念
1、同步任務(wù)+串行隊(duì)列
- (void)syncSerial{
dispatch_queue_t queue = dispatch_queue_create(projectId, DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"任務(wù)一---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務(wù)二---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務(wù)三---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務(wù)四---當(dāng)前線程%@",[NSThread currentThread]);
});
}

結(jié)論: 同步函數(shù)+串行隊(duì)列,不創(chuàng)建新線程,順序執(zhí)行
2、 同步函數(shù)+并發(fā)隊(duì)列
- (void)syncConcurrent{
dispatch_queue_t queue = dispatch_queue_create(projectId, DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"任務(wù)一---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務(wù)二---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務(wù)三---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務(wù)四---當(dāng)前線程%@",[NSThread currentThread]);
});
}

結(jié)論:同步函數(shù)+并發(fā)隊(duì)列 不開啟新線程,順序執(zhí)行
3、異步函數(shù)+串行隊(duì)列
- (void)asyncSerial{
dispatch_queue_t queue = dispatch_queue_create(projectId, DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"任務(wù)一---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)二---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)三---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)四---當(dāng)前線程%@",[NSThread currentThread]);
});
}

結(jié)論: 異步函數(shù)+串行隊(duì)列 開啟新線程,順序執(zhí)行任務(wù)
4、 異步函數(shù)+并發(fā)隊(duì)列
- (void)asyncConcurren{
NSLog(@"開始任務(wù)");
dispatch_queue_t queue = dispatch_queue_create(projectId, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任務(wù)一---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)二---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)三---當(dāng)前線程%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)四---當(dāng)前線程%@",[NSThread currentThread]);
});
}

結(jié)論: 異步函數(shù)+并發(fā)隊(duì)列 創(chuàng)建新線程,并發(fā)執(zhí)行任務(wù)
通過以上實(shí)驗(yàn)可以看出,對(duì)于串行隊(duì)列,任務(wù)都是順序執(zhí)行的,只不過異步任務(wù)會(huì)新創(chuàng)建一個(gè)線程,同步任務(wù)在當(dāng)前線程,但是多個(gè)異步任務(wù)都在同一個(gè)新創(chuàng)建的線程中。
對(duì)于并發(fā)隊(duì)列,同步任務(wù)不會(huì)創(chuàng)建新的線程,順序執(zhí)行,異步任務(wù)會(huì)創(chuàng)建多個(gè)子線程,而且是并行執(zhí)行的。
在這里解釋一下并發(fā)和并行的區(qū)別,并發(fā)是指在一個(gè)時(shí)間段內(nèi)可以處理多個(gè)任務(wù),但這些任務(wù)如果以微觀視角來看還是順序執(zhí)行的,只不過CPU的處理速度極快,我們感覺不出來。而并行是真正的同時(shí)處理多個(gè)任務(wù),可以想象是多條公路上同時(shí)有汽車在跑。
對(duì)于并發(fā)隊(duì)列,雖然可以同時(shí)處理多個(gè)任務(wù),但還是遵循FIFO原則的。如果最多只能創(chuàng)建四個(gè)子線程,分別正在執(zhí)行四個(gè)任務(wù),這時(shí)有第五個(gè)任務(wù)加入了隊(duì)列,它由最先處理完任務(wù)的線程來處理,否則就得等待。也就是說從單個(gè)線程的處理過程看是串行的,也是FIFO的。同理多個(gè)串行隊(duì)列同時(shí)執(zhí)行任務(wù)也可以認(rèn)為是并行的。
另外常用的API還有柵欄函數(shù),它的功能用一句話總結(jié):保證它之前的任務(wù)先于它執(zhí)行,它后面的任務(wù)后于它執(zhí)行,文藝點(diǎn)說是起到承上啟下的作用。
- (void)asyncBarrier{
dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"讀取任務(wù)1");
});
dispatch_async(queue, ^{
NSLog(@"讀取任務(wù)2");
});
dispatch_async(queue, ^{
NSLog(@"讀取任務(wù)3");
});
dispatch_barrier_async(queue, ^{
NSLog(@"寫入");
});
dispatch_async(queue, ^{
NSLog(@"讀取任務(wù)4");
});
dispatch_async(queue, ^{
NSLog(@"讀取任務(wù)5");
});
dispatch_async(queue, ^{
NSLog(@"讀取任務(wù)6");
});
}

通過執(zhí)行結(jié)果可以看到,讀取任務(wù)1、2、3的執(zhí)行順序不固定,但一定優(yōu)于dispatch_barrier_async中的任務(wù)執(zhí)行,讀取任務(wù)4、5、6的執(zhí)行順序不固定,但一定遲于dispatch_barrier_async中的任務(wù)執(zhí)行。就像柵欄一樣將這些任務(wù)分割開。
dispatch_barrier_(a)sync使用時(shí)需要注意,它只對(duì)我們自己創(chuàng)建的隊(duì)列有作用,對(duì)于系統(tǒng)的隊(duì)列(主隊(duì)列、全局隊(duì)列)和dispatch_(a)sync功能相同。
三、死鎖發(fā)生的場(chǎng)景
造成死鎖的四個(gè)必要條件:
互斥:至少有一個(gè)資源必須處于非共享模式,即一次只有一個(gè)進(jìn)程可使用。如果另一進(jìn)程申請(qǐng)?jiān)撡Y源,那么申請(qǐng)進(jìn)程應(yīng)等到該資源釋放為止。
占有并等待:—個(gè)進(jìn)程應(yīng)占有至少一個(gè)資源,并等待另一個(gè)資源,而該資源為其他進(jìn)程所占有。
非搶占:資源不能被搶占,即資源只能被進(jìn)程在完成任務(wù)后自愿釋放。
循環(huán)等待:有一組等待進(jìn)程 {P0,P1,…,Pn},P0 等待的資源為 P1 占有,P1 等待的資源為 P2 占有,……,Pn-1 等待的資源為 Pn 占有,Pn 等待的資源為 P0 占有。
在iOS開發(fā)中發(fā)生死鎖,一般情況下資源是不可搶占的,而且既然造成了死鎖那我們分析的重點(diǎn)在于是如何發(fā)生相互等待的。
在viewDidLoad方法中添加如下代碼,這是在主隊(duì)列中添加同步任務(wù)造成的死鎖

原因就在于viewDidLoad方法的代碼塊中是一個(gè)任務(wù)(調(diào)用時(shí)需要等待返回所以是同步任務(wù),只是個(gè)人理解),是系統(tǒng)添加到主隊(duì)列中的,而在該任務(wù)又將圖中的一個(gè)block(可以看做另一個(gè)任務(wù))添加到了主隊(duì)列中,而且是個(gè)同步任務(wù),該任務(wù)默認(rèn)添加到隊(duì)列尾部。根據(jù)之前的概念分析,主隊(duì)列是一個(gè)特殊的串行隊(duì)列,而且只包含主線程,因此一次只能處理一個(gè)任務(wù),后面的任務(wù)必須等待當(dāng)前任務(wù)執(zhí)行完畢才可以執(zhí)行。block中的任務(wù)等待viewDidLoad中的任務(wù)執(zhí)行,而viewDidLoad任務(wù)以同步方式添加了block中的任務(wù),需要等待同步任務(wù)返回才能繼續(xù)執(zhí)行,造成了循環(huán)等待。
如果你還是不明白,請(qǐng)看下面的例子
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任務(wù)1
dispatch_async(queue, ^{
NSLog(@"2----當(dāng)前線程%@",[NSThread currentThread]); // 任務(wù)2
//在當(dāng)前block這個(gè)任務(wù)里面創(chuàng)建同步任務(wù),是希望等待該任務(wù)返回后再執(zhí)行后面的內(nèi)容,即便后面沒有代碼執(zhí)行,但是剛添加的任務(wù)在隊(duì)列尾部,等待隊(duì)列中靠前的任務(wù)執(zhí)行。
dispatch_sync(queue, ^{
NSLog(@"3----當(dāng)前線程%@",[NSThread currentThread]); // 任務(wù)3
});
NSLog(@"4----當(dāng)前線程%@--%d",[NSThread currentThread],count); // 任務(wù)4
});
NSLog(@"5"); // 任務(wù)5
上面這段代碼乍一看沒什么問題,但是一運(yùn)行就會(huì)報(bào)錯(cuò),不信的小伙伴可以試一試


我們來仔細(xì)分析一下上面這段代碼這些任務(wù)的執(zhí)行順序,這樣就可以找到到底是哪個(gè)地方在循環(huán)等待
首先在主線程中創(chuàng)建了一個(gè)串行隊(duì)列,然后執(zhí)行任務(wù)一,將異步任務(wù)添加到串行隊(duì)列中馬上返回(因?yàn)槭钱惒剿韵到y(tǒng)會(huì)幫我們創(chuàng)建一個(gè)子線程來執(zhí)行block里面的任務(wù)),執(zhí)行任務(wù)五,異步任務(wù)在新的子線程中執(zhí)行任務(wù)二,因此任務(wù)五和任務(wù)二的執(zhí)行順序不確定。后面就沒有主隊(duì)列的什么事了,我們創(chuàng)建的串行隊(duì)列中是執(zhí)行的子線程任務(wù),不會(huì)牽扯到主線程。
任務(wù)二執(zhí)行完畢后,將同步任務(wù)添加到串行隊(duì)列,默認(rèn)添加到隊(duì)列尾部,但是在這里為什么不會(huì)執(zhí)行任務(wù)三而是發(fā)生死鎖呢。這說明任務(wù)三在等待它之前的任務(wù)執(zhí)行,但恰好被等待的任務(wù)同步添加了任務(wù)三又在等待任務(wù)三執(zhí)行并返回,所以造成了死鎖(劃重點(diǎn),判斷死鎖的思路)。所以我們要找到是哪個(gè)任務(wù)在任務(wù)三前面并且同步添加了任務(wù)三。
其實(shí)這里需要仔細(xì)區(qū)分一下任務(wù)這個(gè)概念,GCD在添加任務(wù)時(shí)是以block為單位的,也就是說一個(gè)block內(nèi)的代碼塊就是一個(gè)任務(wù)(即使代碼塊為空也是一個(gè)任務(wù),大家可以想一想為什么viewDidLoad里面只有同步往主隊(duì)列添加任務(wù)的代碼也會(huì)造成死鎖了),而我們通常打印的這些任務(wù)一、任務(wù)二這些是block內(nèi)的子任務(wù),也可以說是對(duì)block內(nèi)任務(wù)的一個(gè)劃分,只是為了方便描述?;氐轿覀兊拇a,在串行隊(duì)列中異步添加的block就是一個(gè)任務(wù),當(dāng)在處理這個(gè)任務(wù)的時(shí)候,它又申請(qǐng)?zhí)砑恿送饺蝿?wù)任務(wù)三,需要等待任務(wù)三的返回,但是任務(wù)三在隊(duì)尾,等待block任務(wù)執(zhí)行,所以造成了循環(huán)等待。
到此為止就給大家分析完了,當(dāng)然造成死鎖還會(huì)有其他情況,不過我暫時(shí)沒有遇到,我認(rèn)為只要分析透徹幾種典型情況并掌握方法,在以后遇到問題時(shí)就不會(huì)手足無措了。
后面是一些常用的API,喜歡的童鞋可以拿走。
四、常用API總結(jié)
/*
* Main Dispatch Queue 的獲取方法
*/
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
/*
* Global Dispatch Queue (高優(yōu)先級(jí))的獲取方法
*/
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
/*
* Global Dispatch Queue (默認(rèn)優(yōu)先級(jí))的獲取方法
*/
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* Global Dispatch Queue (低優(yōu)先級(jí))的獲取方法
*/
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
/*
* Global Dispatch Queue (后臺(tái)優(yōu)先級(jí))的獲取方法
*/
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
// 一個(gè)異步的任務(wù),例如網(wǎng)絡(luò)請(qǐng)求,耗時(shí)的文件操作等等
...
dispatch_async(dispatch_get_main_queue(), ^{
// UI刷新
...
});
});
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行一次的任務(wù),例如創(chuàng)建單例
...
});
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, ^{
// 異步任務(wù)1
});
dispatch_group_async(group, queue, ^{
// 異步任務(wù)2
});
// 等待group中多個(gè)異步任務(wù)執(zhí)行完畢,做一些事情
dispatch_group_notify(group, mainQueue, ^{
// 任務(wù)完成后,在主隊(duì)列中做一些操作,相當(dāng)于做完所有事情后總結(jié),總是在最后執(zhí)行,和這段代碼的添加順序無關(guān)
...
});
參考文章
GCD容易讓人迷惑的幾個(gè)小問題
iOS GCD全析(一)(這一系列的文件簡(jiǎn)直就是我的掃盲篇,大贊作者大大,希望可以幫到像我這樣的小白)