1.隊(duì)列
串行隊(duì)列,串行隊(duì)列將任務(wù)以先進(jìn)先出(FIFO)的順序來(lái)執(zhí)行,所以串行隊(duì)列經(jīng)常用來(lái)做訪問(wèn)某些特定資源的同步處理。你可以也根據(jù)需要?jiǎng)?chuàng)建多個(gè)隊(duì)列,而這些隊(duì)列相對(duì)其他隊(duì)列都是并發(fā)執(zhí)行的。換句話說(shuō),如果你創(chuàng)建了4個(gè)串行隊(duì)列,每一個(gè)隊(duì)列在同一時(shí)間都只執(zhí)行一個(gè)任務(wù),對(duì)這四個(gè)任務(wù)來(lái)說(shuō),他們是相互獨(dú)立且并發(fā)執(zhí)行的。如果需要?jiǎng)?chuàng)建串行隊(duì)列,一般用dispatch_queue_create這個(gè)方法來(lái)實(shí)現(xiàn),并指定隊(duì)列類型DISPATCH_QUEUE_SERIAL。
并發(fā)隊(duì)列,并發(fā)隊(duì)列雖然是能同時(shí)執(zhí)行多個(gè)任務(wù),但這些任務(wù)仍然是按照先到先執(zhí)行(FIFO)的順序來(lái)執(zhí)行的。并發(fā)隊(duì)列會(huì)基于系統(tǒng)負(fù)載來(lái)合適地選擇并發(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í)來(lái)訪問(wèn)隊(duì)列。當(dāng)然我們也可以用dispatch_queue_create,并指定隊(duì)列類型DISPATCH_QUEUE_CONCURRENT,來(lái)自己創(chuàng)建一個(gè)并發(fā)隊(duì)列。
主隊(duì)列,與主線程功能相同。實(shí)際上,提交至main queue的任務(wù)會(huì)在主線程中執(zhí)行。main queue可以調(diào)用dispatch_get_main_queue()來(lái)獲得。因?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í)行,但是也沒(méi)什么意義。
異步任務(wù),使用dispatch_async將任務(wù)加入隊(duì)列。將異步任務(wù)加入串行隊(duì)列,會(huì)順序執(zhí)行,并且不會(huì)出現(xiàn)死鎖問(wèn)題。將異步任務(wù)加入并行隊(duì)列,會(huì)并行執(zhí)行多個(gè)任務(wù),這也是我們最常用的一種方式。
3.GCD常見(jiàn)的用法和應(yīng)用場(chǎng)景
3.1 dispatch_async
(常見(jiàn)的應(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í)差距比較大)。比如這可以用來(lái)拉取網(wǎng)絡(luò)數(shù)據(jù)后提前算出各個(gè)控件的大小,防止繪制時(shí)計(jì)算,提高表單滑動(dòng)流暢性,如果用for循環(huán),耗時(shí)較多,并且每個(gè)表單的數(shù)據(jù)沒(méi)有依賴關(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é)果無(wú)影響環(huán)境),輸出順序不定,比如1098673452
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函數(shù)說(shuō)明
*
* @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ì)列中沒(méi)有執(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è)線程訪問(wèn)共有資源時(shí)候,會(huì)因?yàn)槎嗑€程的特性而引發(fā)數(shù)據(jù)出錯(cuò)的問(wèn)題。
// dispatch_semaphore_signal有兩類用法:a、解決同步問(wèn)題;b、解決有限資源訪問(wèn)(資源為1,即互斥)問(wèn)題。
// dispatch_semaphore_wait,若semaphore計(jì)數(shù)為0則等待,大于0則使其減1。
// dispatch_semaphore_signal使semaphore計(jì)數(shù)加1。
// a、同步問(wèn)題:輸出肯定為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、有限資源訪問(wèn)問(wèn)題: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語(yǔ)言接口形式的,所以其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語(yǔ)言的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. 常見(jiàn)的死鎖
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一樣的死鎖問(wèn)題。