1.基本介紹
什么是GCD
Grand Central Dispatch (GCD) 是異步執(zhí)行任務(wù)的技術(shù)之一框仔。開(kāi)發(fā)者只需要定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue中,GCD就能生成必要的線(xiàn)程并計(jì)劃執(zhí)行任務(wù)。
GCD的優(yōu)勢(shì)
- GCD會(huì)自動(dòng)的利用多核(比如雙核淆攻,四核)
- GCD會(huì)自動(dòng)管理線(xiàn)程的生命周期(創(chuàng)建線(xiàn)程笋敞、調(diào)度任務(wù)音半、銷(xiāo)毀線(xiàn)程)
- GCD會(huì)自動(dòng)根據(jù)系統(tǒng)負(fù)載來(lái)增減線(xiàn)程數(shù)量
Dispatch Queues
GCD的基本概念就是dispatch queue琳袄。dispatch queue是一個(gè)對(duì)象,它可以接受任務(wù)澎办,并將任務(wù)以先到先執(zhí)行的順序來(lái)執(zhí)行嘲碱。dispatch queue可以是并發(fā)的或串行的。并發(fā)任務(wù)會(huì)像NSOperationQueue那樣基于系統(tǒng)負(fù)載來(lái)合適地并發(fā)進(jìn)行局蚀,串行隊(duì)列同一時(shí)間只執(zhí)行單一任務(wù)麦锯。
- 串行隊(duì)列:按FIFO每次取出一個(gè)任務(wù)執(zhí)行,當(dāng)前一個(gè)任務(wù)完成后才會(huì)取出第二個(gè)任務(wù)琅绅。
-
并發(fā)隊(duì)列:按FIFO取出任務(wù)執(zhí)行扶欣,但是不會(huì)等待前一個(gè)任務(wù)完成就會(huì)取出第二個(gè)任務(wù)。
SerialQueue.png
任務(wù)
- 同步任務(wù):同步執(zhí)行,會(huì)阻塞當(dāng)前線(xiàn)程料祠,直到當(dāng)前的block任務(wù)執(zhí)行完畢骆捧。
- 異步任務(wù):異步執(zhí)行,不會(huì)阻塞當(dāng)前線(xiàn)程髓绽。
隊(duì)列與任務(wù)的組合情況
同步任務(wù) | 異步任務(wù) | |
---|---|---|
主隊(duì)列 | 在主隊(duì)列添加同步任務(wù)會(huì)死鎖 | 不開(kāi)啟新線(xiàn)程敛苇,在主線(xiàn)程按序執(zhí)行任務(wù) |
串行隊(duì)列 | 不開(kāi)啟新線(xiàn)程,在當(dāng)前線(xiàn)程按序執(zhí)行任務(wù) | 開(kāi)啟一條線(xiàn)程顺呕,在這個(gè)線(xiàn)程中按序執(zhí)行任務(wù) |
并發(fā)隊(duì)列/全局并發(fā)隊(duì)列 | 不開(kāi)啟新線(xiàn)程枫攀,在當(dāng)前線(xiàn)程按序執(zhí)行任務(wù) | GCD根據(jù)系統(tǒng)資源開(kāi)啟多條線(xiàn)程執(zhí)行任務(wù) |
小結(jié)
- 同步和異步?jīng)Q定了是否開(kāi)啟新的線(xiàn)程。main隊(duì)列除外株茶,在main隊(duì)列中来涨,同步或者異步執(zhí)行都不會(huì)另開(kāi)線(xiàn)程。
- 串行和并行启盛,決定了任務(wù)是否同時(shí)執(zhí)行扫夜。
- 不要在執(zhí)行串行隊(duì)列的線(xiàn)程中向當(dāng)前的串行隊(duì)列添加同步任務(wù),會(huì)導(dǎo)致死鎖驰徊。
2.GCD使用
獲取Dispatch Queue的方法有兩種。
第一種方法是通過(guò)GCD的API生成Dispatch Queue堕阔。
dispatch_queue_t queue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
第一個(gè)參數(shù)指定該隊(duì)列的名稱(chēng)棍厂,該名稱(chēng)會(huì)出現(xiàn)應(yīng)用程序崩潰時(shí)產(chǎn)生的CrashLog中,在調(diào)用過(guò)程中我們也可以使用dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)
獲取當(dāng)前隊(duì)列的名字超陆。
第二個(gè)參數(shù)指定該隊(duì)列的類(lèi)型牺弹。
DISPATCH_QUEUE_SERIAL
或者NULL表示該隊(duì)列是串行隊(duì)列,
DISPATCH_QUEUE_CONCURRENT
表示該隊(duì)列是并發(fā)隊(duì)列时呀。
第二種方法是獲取系統(tǒng)標(biāo)準(zhǔn)提供的Dispatch Queue张漂。
系統(tǒng)會(huì)給我們提供Main Dispatch Queue(主隊(duì)列)和Global Dispatch Queue(全局并發(fā)隊(duì)列)。
Main Dispatch Queue
Main Dispatch Queue顧名思義谨娜,是在主線(xiàn)程中執(zhí)行的隊(duì)列航攒。因?yàn)橹骶€(xiàn)程只有一個(gè),所以主隊(duì)列自然就是串行隊(duì)列趴梢。追加到主隊(duì)列的處理在主線(xiàn)程的RunLoop中執(zhí)行漠畜,因此要將用戶(hù)界面的更新等一些必須在主線(xiàn)程中執(zhí)行的處理追加到主隊(duì)列中使用。
// Main 隊(duì)列獲取方法
dispatch_queue_t mainQueue = dispatch_get_main_queue()坞靶;
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
在上面說(shuō)過(guò)憔狞,同步會(huì)阻塞當(dāng)前線(xiàn)程,執(zhí)行完block里面任務(wù)才會(huì)繼續(xù)往下走彰阴。dispatch_sync阻塞了主線(xiàn)程瘾敢,然后把任務(wù)追到加主隊(duì)列,并在主線(xiàn)程執(zhí)行,但是此時(shí)的主線(xiàn)程已經(jīng)被阻塞簇抵,所以block任務(wù)也無(wú)法執(zhí)行庆杜,block任務(wù)不執(zhí)行,dispatch_sync會(huì)繼續(xù)阻塞主線(xiàn)程正压。這樣子就產(chǎn)生了死鎖欣福。
Global Dispatch Queue
Global Dispatch Queue是全局并發(fā)隊(duì)列,沒(méi)有必要通過(guò)dispatch_queue_create函數(shù)逐個(gè)生成并發(fā)焦履,只要獲取全局并發(fā)隊(duì)列使用即可拓劝。
全局并發(fā)隊(duì)列有4個(gè)執(zhí)行優(yōu)先級(jí),分別是高優(yōu)先級(jí)(High Priority)嘉裤,默認(rèn)優(yōu)先級(jí)(Default Priority)郑临,低優(yōu)先級(jí)(Low Priority)和后臺(tái)優(yōu)先級(jí)(Background Priority)。
// 獲取高優(yōu)先級(jí)
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
// 獲取默認(rèn)優(yōu)先級(jí)
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 獲取低優(yōu)先級(jí)
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
// 獲取后臺(tái)優(yōu)先級(jí)
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue
dispatch_queue_create函數(shù)生成的隊(duì)列不管是串行隊(duì)列還是并發(fā)隊(duì)列屑宠,都使用跟默認(rèn)優(yōu)先級(jí)的全局并發(fā)隊(duì)列相同的優(yōu)先級(jí)厢洞。而設(shè)置一個(gè)隊(duì)列的優(yōu)先級(jí)可以使用dispatch_set_target_queue。
dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.serialQueue", NULL);
dispatch_queue_t backgroundGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(serialQueue, backgroundGlobalQueue);
指定要變更優(yōu)先級(jí)的隊(duì)列為第一個(gè)參數(shù)典奉,指定參考隊(duì)列為第二個(gè)參數(shù)躺翻。
dispatch_set_target_queue
函數(shù)還可以改變隊(duì)列的執(zhí)行層次。在多個(gè)串行隊(duì)列中卫玖,使用dispatch_set_target_queue
函數(shù)指定目標(biāo)為某一串行隊(duì)列公你,那么原本應(yīng)并行執(zhí)行的多個(gè)串行隊(duì)列,在目標(biāo)串行隊(duì)列上只能同時(shí)執(zhí)行一個(gè)任務(wù)假瞬。在必須要將不可并發(fā)執(zhí)行的處理追加到多個(gè)串行隊(duì)列中時(shí)陕靠,可以使用dispatch_set_target_queue
函數(shù)防止并發(fā)執(zhí)行。
dispatch_queue_t serialQueue1 = dispatch_queue_create("serialQueue1", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("serialQueue2", NULL);
dispatch_queue_t serialQueue3 = dispatch_queue_create("serialQueue3", NULL);
dispatch_queue_t serialQueue4 = dispatch_queue_create("serialQueue4", NULL);
dispatch_set_target_queue(serialQueue2, serialQueue1);
dispatch_set_target_queue(serialQueue3, serialQueue1);
dispatch_set_target_queue(serialQueue4, serialQueue1);
dispatch_async(serialQueue2, ^{
NSLog(@"%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
dispatch_async(serialQueue3, ^{
NSLog(@"%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
dispatch_async(serialQueue4, ^{
NSLog(@"%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
//輸出結(jié)果為
serialQueue2
serialQueue3
serialQueue4
dispatch_after
想在指定時(shí)間后執(zhí)行的情況脱茉,可使用dispatch_after
函數(shù)來(lái)實(shí)現(xiàn)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"等待3秒");
});
使用dispatch_after
需要注意的是剪芥,這個(gè)函數(shù)并不是在指定時(shí)間后執(zhí)行,而是在指定時(shí)間把任務(wù)追加到指定的隊(duì)列中琴许。如上面的代碼税肪,3秒后只是把任務(wù)追加到主隊(duì)列當(dāng)中,具體執(zhí)行時(shí)間與主線(xiàn)程擁塞程度有關(guān)榜田。
Dispatch Group
我們經(jīng)常會(huì)有這樣的需求寸认,在多個(gè)追加到Dispatch Queue中的處理執(zhí)行完畢后,進(jìn)行一些操作串慰。在使用串行隊(duì)列的時(shí)候偏塞,我們只需要在最后追加結(jié)束后的處理。但是在使用多個(gè)并發(fā)隊(duì)列或同時(shí)使用多個(gè)隊(duì)列時(shí)邦鲫,就需要使用Dispatch Group灸叼。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block3");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
//執(zhí)行結(jié)果
block1
block3
block2
done
很明顯神汹,這種方式是不阻塞的。由于我們是異步把任務(wù)添加到隊(duì)列中古今,所以任務(wù)執(zhí)行的順序是不一定的屁魏。但是dispatch_group_notify里面的block肯定是最后執(zhí)行。
如果想要阻塞線(xiàn)程可以使用dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
第二個(gè)參數(shù)為等待時(shí)間DISPATCH_TIME_FOREVER
表示永遠(yuǎn)等待捉腥。
當(dāng)任務(wù)中有completion block
時(shí)氓拼,這種任務(wù)是馬上完成的,例如網(wǎng)絡(luò)請(qǐng)求抵碟。但是我們想讓任務(wù)在收到completion block
時(shí)才完成桃漾,這時(shí)需要我們手動(dòng)管理任務(wù)的開(kāi)始和結(jié)束。
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[Request requestWithSuccess:^{
NSLog(@"success");
dispatch_group_leave(group);
} failBlk:^{
NSLog(@"fail");
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[Request requestWithSuccess:^{
NSLog(@"success");
dispatch_group_leave(group);
} failBlk:^{
NSLog(@"fail");
dispatch_group_leave(group);
}];
dispatch_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"全部完成");
});
通過(guò)dispatch_group_enter
拟逮,dispatch_group_leave
兩個(gè)函數(shù)可以實(shí)現(xiàn)進(jìn)入,退出兩個(gè)動(dòng)作撬统。要注意enter
和leave
要成對(duì)出現(xiàn),否則group永遠(yuǎn)不會(huì)結(jié)束敦迄。
dispatch_barrier_async
在訪問(wèn)數(shù)據(jù)時(shí)恋追,使用串行隊(duì)列可以避免數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。寫(xiě)入處理不可與其他的寫(xiě)入處理以及包含讀取處理的其他某些處理并行執(zhí)行罚屋,但是讀取處理只和讀取處理并行執(zhí)行苦囱,那么多個(gè)并行執(zhí)行就不會(huì)發(fā)生問(wèn)題。也就是說(shuō)脾猛,為了高效率的訪問(wèn)撕彤,需要實(shí)現(xiàn)多讀單寫(xiě)。
GCD為我們提供了一種方便的實(shí)現(xiàn)dispatch_barrier_async
尖滚,它等待所有位于barrier函數(shù)之前的操作執(zhí)行完畢后執(zhí)行,并且在barrier函數(shù)執(zhí)行完成后,barrier函數(shù)之后的操作才會(huì)得到執(zhí)行,該函數(shù)需要同dispatch_queue_create函數(shù)生成的并發(fā)隊(duì)列一起使用。
dispatch_queue_t queue = dispatch_queue_create("queueForBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任務(wù)1");
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)2");
});
dispatch_barrier_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"任務(wù)3 barrier");
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)4");
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)5");
});
//輸出結(jié)果
任務(wù)2
任務(wù)1
任務(wù)3 barrier
任務(wù)4
任務(wù)5
dispatch_apply
dispatch_apply
函數(shù)按指定的次數(shù)降block追加到指定的隊(duì)列中瞧柔,并阻塞線(xiàn)程等待全部處理執(zhí)行結(jié)束漆弄。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSArray *array = @[@"1",@"2",@"3"];
dispatch_apply(array.count, queue, ^(size_t index) {
NSLog(@"%@",array[index]);
});
NSLog(@"完成");
//輸出結(jié)果
2
1
3
完成
由于dispatch_apply
函數(shù)會(huì)等待所有處理執(zhí)行結(jié)束,所以最好在dispatch_async
函數(shù)中非同步的執(zhí)行dispatch_apply
函數(shù)
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSArray *array = @[@"1",@"2",@"3"];
dispatch_async(queue, ^{
dispatch_apply(array.count, queue, ^(size_t index) {
NSLog(@"%@",array[index]);
});
dispatch_async(dispatch_get_main_queue(), ^{
//回到主線(xiàn)程
});
});
dispatch_suspend/dispatch_resume
//掛起指定的隊(duì)列
dispatch_suspend(queue);
//恢復(fù)指定的隊(duì)列
dispatch_resume(queue);
dispatch_suspend
函數(shù)對(duì)已經(jīng)執(zhí)行的處理沒(méi)有影響造锅,隊(duì)列中未執(zhí)行的處理會(huì)被掛起撼唾,dispatch_resume
函數(shù)會(huì)恢復(fù)這些處理的執(zhí)行。
Dispatch Semaphore
信號(hào)量可以控制同時(shí)訪問(wèn)資源的數(shù)量哥蔚,解決資源爭(zhēng)奪的問(wèn)題倒谷。信號(hào)量持有計(jì)數(shù),計(jì)數(shù)為0等待糙箍,計(jì)數(shù)大于或等于1時(shí)渤愁,減去1而不等待。類(lèi)似于過(guò)安檢時(shí)深夯,允許n個(gè)人一起安檢抖格,每當(dāng)一個(gè)人安檢完成诺苹,則下一個(gè)人可以進(jìn)行安檢。
//創(chuàng)建計(jì)數(shù)值為1的信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSArray *array = @[@"1",@"2",@"3",@"4",@"5"];
for (NSString *string in array) {
dispatch_async(queue, ^{
//dispatch_semaphore_wait函數(shù)等待信號(hào)量的計(jì)數(shù)值大于或等于1時(shí)雹拄,對(duì)計(jì)數(shù)減1并向下執(zhí)行收奔,否則等待。
//DISPATCH_TIME_FOREVER表示永遠(yuǎn)等待滓玖。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[NSThread sleepForTimeInterval:1
NSLog(@"%@",string);
//dispatch_semaphore_signal函數(shù)將信號(hào)量的計(jì)數(shù)加1
dispatch_semaphore_signal(semaphore);
});
}
上面代碼創(chuàng)建了一個(gè)計(jì)數(shù)初始值為1的信號(hào)量坪哄,然后向全局并發(fā)隊(duì)列中添加了5個(gè)任務(wù),當(dāng)有一個(gè)任務(wù)通過(guò)dispatch_semaphore_wait
函數(shù)時(shí)势篡,信號(hào)量的計(jì)數(shù)被減1翩肌,此時(shí)計(jì)數(shù)為0,剩余的任務(wù)則會(huì)等待殊霞,直到dispatch_semaphore_signal
將計(jì)數(shù)加1摧阅。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0) {
//在等待時(shí)間內(nèi)計(jì)數(shù)值達(dá)到大于等于1,減1繼續(xù)處理
}
else{
//在等待時(shí)間內(nèi)計(jì)數(shù)值為0
}
我們也可以通過(guò)dispatch_semaphore_wait
函數(shù)的返回值進(jìn)行分支處理绷蹲。當(dāng)我們給定一個(gè)等待時(shí)間棒卷,如果信號(hào)量的計(jì)數(shù)值在等待時(shí)間內(nèi)達(dá)到大于等于1,dispatch_semaphore_wait
返回0祝钢,如果超過(guò)這個(gè)時(shí)間計(jì)數(shù)值還為0比规,則返回值不為0。
dispatch_once
dispatch_once
函數(shù)保證在應(yīng)用程序執(zhí)行中只執(zhí)行一次處理拦英。并且在多線(xiàn)程環(huán)境下能保證安全蜒什。
@implementation Manager
+ (Manager *)sharedInstance
{
static Manage *manager = nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
manager = [[Manager alloc] init];
});
return manager;
}
我們一般用來(lái)創(chuàng)建單例。
Dispatch Source
Dispatch Source的種類(lèi)
名稱(chēng) | 種類(lèi) |
---|---|
DISPATCH_SOURCE_TYPE_DATA_ADD | 自定義事件疤估,變量增加 |
DISPATCH_SOURCE_TYPE_DATA_OR | 自定義事件灾常,變量OR |
DISPATCH_SOURCE_TYPE_MACH_SEND | MACH 端口發(fā)送 |
DISPATCH_SOURCE_TYPE_MACH_RECV | MACH 端口接收 |
DISPATCH_SOURCE_TYPE_PROC | 檢測(cè)到與進(jìn)程相關(guān)的事件 |
DISPATCH_SOURCE_TYPE_READ | IO操作,如對(duì)文件的操作铃拇、socket操作的讀響應(yīng) |
DISPATCH_SOURCE_TYPE_SIGNAL | 接受信號(hào) |
DISPATCH_SOURCE_TYPE_TIMER | 定時(shí)器 |
DISPATCH_SOURCE_TYPE_VNODE | 文件系統(tǒng)有變更 |
DISPATCH_SOURCE_TYPE_WRITE | IO操作钞瀑,如對(duì)文件的操作、socket操作的寫(xiě)響應(yīng) |
使用DISPATCH_SOURCE_TYPE_TIMER的定時(shí)器的例子
//倒計(jì)時(shí)時(shí)間
__block int timeout = 60;
//創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//創(chuàng)建timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//設(shè)置開(kāi)始時(shí)間未當(dāng)前時(shí)間慷荔,間隔1秒雕什,誤差為0
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC), 0);
//觸發(fā)事件
dispatch_source_set_event_handler(timer, ^{
if (timeout <= 0) {
//取消dispatch source
dispatch_source_cancel(timer);
}
else{
timeout --;
NSLog(@"%d",timeout);
}
});
//啟動(dòng)定時(shí)器
dispatch_resume(timer);