1.隊(duì)列
串行隊(duì)列洗贰,串行隊(duì)列將任務(wù)以先進(jìn)先出(FIFO)的順序來執(zhí)行愿待,所以串行隊(duì)列經(jīng)常用來做訪問某些特定資源的同步處理。你可以也根據(jù)需要?jiǎng)?chuàng)建多個(gè)隊(duì)列然遏,而這些隊(duì)列相對(duì)其他隊(duì)列都是并發(fā)執(zhí)行的扮超。換句話說取刃,如果你創(chuàng)建了4個(gè)串行隊(duì)列,每一個(gè)隊(duì)列在同一時(shí)間都只執(zhí)行一個(gè)任務(wù)出刷,對(duì)這四個(gè)任務(wù)來說璧疗,他們是相互獨(dú)立且并發(fā)執(zhí)行的。如果需要?jiǎng)?chuàng)建串行隊(duì)列馁龟,一般用dispatch_queue_create這個(gè)方法來實(shí)現(xiàn)崩侠,并指定隊(duì)列類型DISPATCH_QUEUE_SERIAL。
并發(fā)隊(duì)列坷檩,并發(fā)隊(duì)列雖然是能同時(shí)執(zhí)行多個(gè)任務(wù)却音,但這些任務(wù)仍然是按照先到先執(zhí)行(FIFO)的順序來執(zhí)行的。并發(fā)隊(duì)列會(huì)基于系統(tǒng)負(fù)載來合適地選擇并發(fā)執(zhí)行這些任務(wù)矢炼。并發(fā)隊(duì)列一般指的就是全局隊(duì)列(Global queue)系瓢,進(jìn)程中存在四個(gè)全局隊(duì)列:高、中(默認(rèn))句灌、低夷陋、后臺(tái)四個(gè)優(yōu)先級(jí)隊(duì)列欠拾,可以調(diào)用dispatch_get_global_queue函數(shù)傳入優(yōu)先級(jí)來訪問隊(duì)列。當(dāng)然我們也可以用dispatch_queue_create骗绕,并指定隊(duì)列類型DISPATCH_QUEUE_CONCURRENT藐窄,來自己創(chuàng)建一個(gè)并發(fā)隊(duì)列。
主隊(duì)列酬土,與主線程功能相同荆忍。實(shí)際上,提交至main queue的任務(wù)會(huì)在主線程中執(zhí)行撤缴。main queue可以調(diào)用dispatch_get_main_queue()來獲得刹枉。因?yàn)閙ain queue是與主線程相關(guān)的,所以這是一個(gè)串行隊(duì)列腹泌。和其它串行隊(duì)列一樣嘶卧,這個(gè)隊(duì)列中的任務(wù)一次只能執(zhí)行一個(gè)尔觉。它能保證所有的任務(wù)都在主線程執(zhí)行凉袱,而主線程是唯一可用于更新 UI 的線程。
2.任務(wù)
同步任務(wù)侦铜,使用dispatch_sync將任務(wù)加入隊(duì)列专甩。將同步任務(wù)加入串行隊(duì)列,會(huì)順序執(zhí)行钉稍,一般不這樣做并且在一個(gè)任務(wù)未結(jié)束時(shí)調(diào)起其它同步任務(wù)會(huì)死鎖涤躲。將同步任務(wù)加入并行隊(duì)列,會(huì)順序執(zhí)行贡未,但是也沒什么意義种樱。
異步任務(wù),使用dispatch_async將任務(wù)加入隊(duì)列俊卤。將異步任務(wù)加入串行隊(duì)列嫩挤,會(huì)順序執(zhí)行,并且不會(huì)出現(xiàn)死鎖問題消恍。將異步任務(wù)加入并行隊(duì)列岂昭,會(huì)并行執(zhí)行多個(gè)任務(wù),這也是我們最常用的一種方式狠怨。
3.GCD常見的用法和應(yīng)用場(chǎng)景
3.1 dispatch_async
(常見的應(yīng)用場(chǎng)景是異步處理耗時(shí)的操作约啊,然后耗時(shí)操作處理完畢后,使用主線程更新UI)
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刷新
...
});
});
3.2 dispatch_after
(常用的應(yīng)用場(chǎng)景是延時(shí)調(diào)用)
dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
// 在queue里面延遲執(zhí)行的一段代碼
...
});
3.3 dispatch_once
(常用于單例的創(chuàng)建,只創(chuàng)建一次)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行一次的任務(wù)
...
});
3.4 dispatch_group
(GCD組憎蛤,把一組任務(wù)提交到隊(duì)列中枢里,多個(gè)請(qǐng)求完畢后才處理事情,如多個(gè)網(wǎng)絡(luò)請(qǐng)求完畢會(huì),才去更新UI)
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í)行完畢栏豺,做一些事情彬碱,介紹兩種方式
// 方式1(不好,會(huì)卡住當(dāng)前線程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
...
// 方式2(比較好)
dispatch_group_notify(group, mainQueue, ^{
// 任務(wù)完成后奥洼,在主隊(duì)列中做一些操作
...
});
3.5 dispatch_barrier_async
(和dispatch_group類似巷疼,dispatch_barrier也是異步任務(wù)間的一種同步方式,可以在比如文件的讀寫操作時(shí)使用灵奖,保證讀操作的準(zhǔn)確性嚼沿。另外,有一點(diǎn)需要注意瓷患,dispatch_barrier_sync和dispatch_barrier_async只在自己創(chuàng)建的并發(fā)隊(duì)列上有效骡尽,在全局(Global)并發(fā)隊(duì)列、串行隊(duì)列上擅编,效果跟dispatch_(a)sync效果一樣)
// dispatch_barrier_async的作用可以用一個(gè)詞概括--承上啟下攀细,它保證此前的任務(wù)都先于自己執(zhí)行,此后的任務(wù)也遲于自己執(zhí)行爱态。本例中谭贪,任務(wù)4會(huì)在任務(wù)1、2锦担、3都執(zhí)行完之后執(zhí)行俭识,而任務(wù)5、6會(huì)等待任務(wù)4執(zhí)行完后執(zhí)行洞渔。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 任務(wù)1
...
});
dispatch_async(queue, ^{
// 任務(wù)2
...
});
dispatch_async(queue, ^{
// 任務(wù)3
...
});
dispatch_barrier_async(queue, ^{
// 任務(wù)4
...
});
dispatch_async(queue, ^{
// 任務(wù)5
...
});
dispatch_async(queue, ^{
// 任務(wù)6
...
});
3.6 dispatch_apply
(dispatch_apply有什么用呢套媚,因?yàn)閐ispatch_apply并行的運(yùn)行機(jī)制,效率一般快于for循環(huán)的類串行機(jī)制(在for一次循環(huán)中的處理任務(wù)很多時(shí)差距比較大)磁椒。比如這可以用來拉取網(wǎng)絡(luò)數(shù)據(jù)后提前算出各個(gè)控件的大小堤瘤,防止繪制時(shí)計(jì)算,提高表單滑動(dòng)流暢性衷快,如果用for循環(huán)宙橱,耗時(shí)較多,并且每個(gè)表單的數(shù)據(jù)沒有依賴關(guān)系蘸拔,所以用dispatch_apply比較好)
// for循環(huán)做一些事情师郑,輸出0123456789
for (int i = 0; i < 10; i ++) {
NSLog(@"%d", i);
}
// dispatch_apply替換(當(dāng)且僅當(dāng)處理順序?qū)μ幚斫Y(jié)果無影響環(huán)境),輸出順序不定调窍,比如1098673452
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函數(shù)說明
*
* @brief dispatch_apply函數(shù)是dispatch_sync函數(shù)和Dispatch Group的關(guān)聯(lián)API
* 該函數(shù)按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等到全部的處理執(zhí)行結(jié)束
*
* @param 10 指定重復(fù)次數(shù) 指定10次
* @param queue 追加對(duì)象的Dispatch Queue
* @param index 帶有參數(shù)的Block, index的作用是為了按執(zhí)行的順序區(qū)分各個(gè)Block
*
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
3.7 dispatch_suspend和dispatch_resume
(隊(duì)列的暫停和恢復(fù)宝冕,已添加到隊(duì)列中沒有執(zhí)行的任務(wù)不會(huì)執(zhí)行,直至等到線程恢復(fù)才會(huì)繼續(xù)執(zhí)行)
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_suspend(queue); //暫停隊(duì)列queue
dispatch_resume(queue); //恢復(fù)隊(duì)列queue
3.8 dispatch_semaphore_signal
dispatch_semaphore 信號(hào)量基于計(jì)數(shù)器的一種多線程同步機(jī)制邓萨。在多個(gè)線程訪問共有資源時(shí)候地梨,會(huì)因?yàn)槎嗑€程的特性而引發(fā)數(shù)據(jù)出錯(cuò)的問題菊卷。
// dispatch_semaphore_signal有兩類用法:a、解決同步問題宝剖;b洁闰、解決有限資源訪問(資源為1,即互斥)問題万细。
// dispatch_semaphore_wait扑眉,若semaphore計(jì)數(shù)為0則等待,大于0則使其減1赖钞。
// dispatch_semaphore_signal使semaphore計(jì)數(shù)加1腰素。
// a、同步問題:輸出肯定為1雪营、2弓千、3。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
// 任務(wù)1
dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
NSLog(@"1\n");
dispatch_semaphore_signal(semaphore2);
dispatch_semaphore_signal(semaphore1);
});
dispatch_async(queue, ^{
// 任務(wù)2
dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
NSLog(@"2\n");
dispatch_semaphore_signal(semaphore3);
dispatch_semaphore_signal(semaphore2);
});
dispatch_async(queue, ^{
// 任務(wù)3
dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
NSLog(@"3\n");
dispatch_semaphore_signal(semaphore3);
});
// b献起、有限資源訪問問題:for循環(huán)看似能創(chuàng)建100個(gè)異步任務(wù)洋访,實(shí)質(zhì)由于信號(hào)限制,最多創(chuàng)建10個(gè)異步任務(wù)征唬。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i ++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
// 任務(wù)
...
dispatch_semaphore_signal(semaphore);
});
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
如果semaphore計(jì)數(shù)大于等于1.計(jì)數(shù)-1捌显,返回茁彭,程序繼續(xù)運(yùn)行总寒。
如果計(jì)數(shù)為0,則等待理肺。
這里設(shè)置的等待時(shí)間是一直等待摄闸。
dispatch_semaphore_signal(semaphore);
計(jì)數(shù)+1.
在這兩句代碼中間的執(zhí)行代碼,每次只會(huì)允許一個(gè)線程進(jìn)入妹萨,這樣就有效的保證了在多線程環(huán)境下年枕,只能有一個(gè)線程進(jìn)入。
3.9 dispatch_set_context乎完、dispatch_get_context和dispatch_set_finalizer_f
(dispatch_set_context可以為隊(duì)列添加上下文數(shù)據(jù)熏兄,但是因?yàn)镚CD是C語言接口形式的,所以其context參數(shù)類型是“void *”树姨。需使用上述abc三種方式創(chuàng)建context摩桶,并且一般結(jié)合dispatch_set_finalizer_f使用,回收context內(nèi)存)
// dispatch_set_context帽揪、dispatch_get_context是為了向隊(duì)列中傳遞上下文context服務(wù)的硝清。
// dispatch_set_finalizer_f相當(dāng)于dispatch_object_t的析構(gòu)函數(shù)。
// 因?yàn)閏ontext的數(shù)據(jù)不是foundation對(duì)象转晰,所以arc不會(huì)自動(dòng)回收芦拿,一般在dispatch_set_finalizer_f中手動(dòng)回收士飒,所以一般講上述三個(gè)方法綁定使用。
- (void)test
{
// 幾種創(chuàng)建context的方式
// a蔗崎、用C語言的malloc創(chuàng)建context數(shù)據(jù)酵幕。
// b硼身、用C++的new創(chuàng)建類對(duì)象死遭。
// c、用Objective-C的對(duì)象郎任,但是要用__bridge等關(guān)鍵字轉(zhuǎn)為Core Foundation對(duì)象他嫡。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
if (queue) {
// "123"即為傳入的context
dispatch_set_context(queue, "123");
dispatch_set_finalizer_f(queue, &xigou);
}
dispatch_async(queue, ^{
char *string = dispatch_get_context(queue);
NSLog(@"%s", string);
});
}
// 該函數(shù)會(huì)在dispatch_object_t銷毀時(shí)調(diào)用番官。
void xigou(void *context)
{
// 釋放context的內(nèi)存(對(duì)應(yīng)上述abc)
// a、CFRelease(context);
// b钢属、free(context);
// c徘熔、delete context;
}
4. 常見的死鎖
4.1 dispatch_sync
// 假設(shè)這段代碼執(zhí)行于主隊(duì)列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 在主隊(duì)列添加同步任務(wù)
dispatch_sync(mainQueue, ^{
// 任務(wù)
...
});
// 在串行隊(duì)列添加同步任務(wù)
dispatch_sync(serialQueue, ^{
// 任務(wù)
...
dispatch_sync(serialQueue, ^{
// 任務(wù)
...
});
};
4.2 dispatch_apply
// 因?yàn)閐ispatch_apply會(huì)卡住當(dāng)前線程,內(nèi)部的dispatch_apply會(huì)等待外部淆党,外部的等待內(nèi)部酷师,所以死鎖。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t) {
// 任務(wù)
...
dispatch_apply(10, queue, ^(size_t) {
// 任務(wù)
...
});
});
4.3 dispatch_barrier
dispatch_barrier_sync在串行隊(duì)列和全局并行隊(duì)列里面和dispatch_sync同樣的效果染乌,所以需考慮同dispatch_sync一樣的死鎖問題山孔。