什么是GCD
Grand Central Dispatch或者GCD,是一套低層API,提供了一種新的方法來進(jìn)行并發(fā)程序編寫。他們都允許程序?qū)⑷蝿?wù)切分為多個(gè)單一任務(wù)然后提交至工作隊(duì)列來并發(fā)地或者串行地執(zhí)行。GCD比之NSOpertionQueue更底層更高效回铛,并且它不是Cocoa框架的一部分。
除了代碼的平行執(zhí)行能力克锣,GCD還提供高度集成的事件控制系統(tǒng)茵肃。可以設(shè)置句柄來響應(yīng)文件描述符袭祟、mach ports(Mach port 用于 OS X上的進(jìn)程間通訊)验残、進(jìn)程、計(jì)時(shí)器巾乳、信號(hào)您没、用戶生成事件。這些句柄通過GCD來并發(fā)執(zhí)行胆绊。
GCD的API很大程度上基于block氨鹏,當(dāng)然,GCD也可以脫離block來使用压状,比如使用傳統(tǒng)c機(jī)制提供函數(shù)指針和上下文指針仆抵。實(shí)踐證明,當(dāng)配合block使用時(shí)种冬,GCD非常簡(jiǎn)單易用且能發(fā)揮其最大能力镣丑。
理解串行、并發(fā)及同步異步
串行和并發(fā)
串行和并發(fā)描述了任務(wù)之間執(zhí)行的時(shí)機(jī)碌廓。任務(wù)如果是串行的传轰,那么在同一時(shí)間只執(zhí)行一個(gè)任務(wù)。并發(fā)的多個(gè)任務(wù)被執(zhí)行的時(shí)候谷婆,可能是在同一時(shí)間。
同步和異步
同步和異步描述了一個(gè)函數(shù)相對(duì)于另一個(gè)函數(shù)何時(shí)執(zhí)行完畢辽聊。同步的函數(shù)只有當(dāng)它調(diào)用的任務(wù)執(zhí)行完纪挎,才會(huì)返回。而異步函數(shù)跟匆,會(huì)立即返回异袄。雖然它也命令任務(wù)執(zhí)行完,但它并不等待任務(wù)執(zhí)行完玛臂。如此烤蜕,異步函數(shù)就不會(huì)阻塞當(dāng)前線程封孙。(這樣說可能有些過于抽象了,個(gè)人理解的是讽营,在同步的時(shí)候沒有開啟子線程的能力虎忌,而在異步的時(shí)候具備開啟子線程的能力)。
隊(duì)列
串行隊(duì)列
1)使用dispatch_queue_create函數(shù)創(chuàng)建串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create(“隊(duì)列名”橱鹏,NULL);
dispatch_queue_t queue = dispatch_queue_create(“隊(duì)列名”膜蠢,DISPATCH_QUEUE_SERIAL);
兩者等效.
2)使用主隊(duì)列(在主隊(duì)列中的任務(wù),都會(huì)放到主線程中執(zhí)行莉兰,它也是唯一一個(gè)允許更新UI的隊(duì)列挑围,所以要是開啟子線程的時(shí)候要更新UI的情況下一定要用主隊(duì)列進(jìn)行更新)
dispatch_queue_t queue = dispatch_get_main_queue();
并發(fā)隊(duì)列(GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊(duì)列)
1)獲得全局隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(dispatch_queue_priority,unsigned long flags);
前一個(gè)參數(shù)是優(yōu)先級(jí)糖荒,有以下幾種:
DISPATCH_QUEUE_PRIORITY_HIGH 2//高
DISPATCH_QUEUE_PRIORITY_DEFAULT 0//默認(rèn)
DISPATCH_QUEUE_PRIORITY_LOW (-2)//低
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MN//后臺(tái)
2)自定義并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create(“隊(duì)列名”杉辙,DISPATCH_QUEUE_CONCURRENT);
在開發(fā)中基本上就是通過dispatch_async和dispatch_sync 分別配合著上面的三種隊(duì)列使用,但是更多的情況下基本上就是dispatch_async和全局隊(duì)列配合使用捶朵,并用主線程中更新UI奏瞬。
dispatch_async
1)全局隊(duì)列
//在這里獲取全局隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task2%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task3%@",[NSThread currentThread]);
});
打印結(jié)果
2016-07-31 21:08:51.114 GCD[81282:8816505] task2<NSThread: 0x7fb36b900080>{number = 2, name = (null)}
2016-07-31 21:08:51.114 GCD[81282:8816516] task3<NSThread: 0x7fb369ff59a0>{number = 3, name = (null)}
2016-07-31 21:08:51.114 GCD[81282:8816512] task1<NSThread: 0x7fb369e2bba0>{number = 4, name = (null)}
可以看出GCD開啟了子線程并且并發(fā)執(zhí)行
2)主隊(duì)列
//在這里獲取主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task2%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task3%@",[NSThread currentThread]);
});
打印結(jié)果
2016-07-31 21:14:37.452 GCD[81293:8820034] task1<NSThread: 0x7ffb7a000720>{number = 1, name = main}
2016-07-31 21:14:37.452 GCD[81293:8820034] task2<NSThread: 0x7ffb7a000720>{number = 1, name = main}
2016-07-31 21:14:37.452 GCD[81293:8820034] task3<NSThread: 0x7ffb7a000720>{number = 1, name = main}
可以看出來雖然用了dispatch_async但是并沒有開啟子線程來執(zhí)行任務(wù),這是因?yàn)殛?duì)列的性質(zhì)決定的泉孩,所以在用主隊(duì)列的時(shí)候要注意硼端。
dispatch_sync
1)全局隊(duì)列
//在這里獲取全局隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_sync(queue, ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"task2%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"task3%@",[NSThread currentThread]);
});
打印結(jié)果
2016-07-31 21:19:06.993 GCD[81311:8822954] task1<NSThread: 0x7f9208e04fe0>{number = 1, name = main}
2016-07-31 21:19:06.994 GCD[81311:8822954] task2<NSThread: 0x7f9208e04fe0>{number = 1, name = main}
2016-07-31 21:19:06.994 GCD[81311:8822954] task3<NSThread: 0x7f9208e04fe0>{number = 1, name = main}
可以看出來dispatch_sync中雖然用的是全局隊(duì)列,但是并沒有并發(fā)寓搬,因?yàn)閐ispatch_sync沒有開啟子線程的能力珍昨,只能在主線程中串行執(zhí)行任務(wù)。
2)主隊(duì)列
//在這里獲取主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"task2%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"task3%@",[NSThread currentThread]);
});
這樣是不會(huì)有打印結(jié)果的句喷,因?yàn)榘l(fā)生了死鎖镣典,在這個(gè)過程中,主線等著GCD運(yùn)行完成后繼續(xù)運(yùn)行而GCD等待著主線運(yùn)行完成后繼續(xù)運(yùn)行唾琼,產(chǎn)生了死鎖兄春,所以不能這樣使用。
dispatch_after
//在這里只有用主隊(duì)列有意義
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
NSLog(@"主線");
打印結(jié)果
2016-07-31 21:37:14.292 GCD[81346:8829855] 主線
2016-07-31 21:37:17.292 GCD[81346:8829855] task1<NSThread: 0x7fee08604ea0>{number = 1, name = main}
可以看出在block中的任務(wù)被延遲了3秒鐘執(zhí)行锡溯。但是這個(gè)是有問題的赶舆,這里只是延時(shí)提交了block,并不是延時(shí)后立即執(zhí)行祭饭。所以dispatch_after不能精準(zhǔn)的控制運(yùn)行狀態(tài)芜茵。
dispatch_once
dispatch_once是一種線程安全的方式,執(zhí)行只執(zhí)行一次代碼塊保證多線程安全倡蝙。
+ (instancetype)sharedManager
{
static ToolManager *sharedPhotoManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[ToolManager alloc] init];
});
return sharedPhotoManager;
}
dispatch_group
有的時(shí)候我們要在完成一些任務(wù)的前提下再去完成后面的任務(wù)九串,這個(gè)時(shí)候就要用到dispatch_group。
1)同步的方式(dispatch_group_wait)(這個(gè)方法會(huì)阻塞線程,后面的代碼要等到wait完成后運(yùn)行)
//由于我們用了同步的方式dispatch_group_wait猪钮,它會(huì)阻塞當(dāng)前線程品山,所以我們?cè)谡麄€(gè)方法外面套上了dispatch_async,使它在后臺(tái)執(zhí)行而不會(huì)阻塞主線程烤低。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
//dispatch_group_enter告知group肘交,一個(gè)任務(wù)開始了。我們需要將dispatch_group_enter與dispatch_group_leave配對(duì)拂玻,否則我們會(huì)遇到莫名其妙的崩潰酸些。
dispatch_group_enter(downloadGroup);
sleep(2);
NSLog(@"task%ld",i);
//dispatch_group_leave告知group,此任務(wù)執(zhí)行完畢檐蚜,要注意與dispatch_group_enter配對(duì)魄懂。
dispatch_group_leave(downloadGroup);
}
//dispatch_group_wait等待所有任務(wù)完成或者超時(shí),在此處我們?cè)O(shè)置等待時(shí)間為永遠(yuǎn)DISPATCH_TIME_FOREVER
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER);
//當(dāng)以上所有任務(wù)執(zhí)行完后闯第,我們?cè)谥麝?duì)列調(diào)用任務(wù)完成的block市栗。
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主線");
});
});
在這里有個(gè)問題,就是說如果我們將其中的超時(shí)時(shí)間設(shè)置成5秒咳短,但是其中的一個(gè)任務(wù)要延時(shí)10秒填帽,那么這個(gè)任務(wù)不會(huì)因?yàn)檫@個(gè)超時(shí)時(shí)間到了停止,還會(huì)繼續(xù)完成任務(wù)咙好,但是不會(huì)等到這個(gè)任務(wù)完成后再去執(zhí)行dispatch_group_wait后面的代碼篡腌。
2)異步的方式(dispatch_group_notify)(不會(huì)阻塞線程,后面的代碼會(huì)執(zhí)行勾效,不用等待這些任務(wù)和notify)
dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_group_async(downloadGroup, dispatch_get_global_queue(0, 0), ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
dispatch_group_async(downloadGroup, dispatch_get_global_queue(0, 0), ^{
NSLog(@"task2%@",[NSThread currentThread]);
});
dispatch_group_async(downloadGroup, dispatch_get_global_queue(0, 0), ^{
NSLog(@"task3%@",[NSThread currentThread]);
});
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSLog(@"主線");
});
其中還涉及到了dispatch_group_enter和dispatch_group_leave嘹悼,我理解這個(gè)就是將任務(wù)添加到隊(duì)列中,并且他們要成對(duì)的出現(xiàn)层宫。他們將任務(wù)添加到當(dāng)前的線程中杨伙,也就是說如果任務(wù)都沒有開啟子線程,那么添加進(jìn)去的就是在當(dāng)前的線程中串行執(zhí)行萌腿,如果任務(wù)開啟了子線程限匣,那么添加進(jìn)去的就是在相對(duì)的子線程可能串行,可能并行(這個(gè)要看隊(duì)列的性質(zhì))
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
});
這兩種的性質(zhì)是一樣的毁菱。
dispatch_apply
dispatch_apply(3, dispatch_get_global_queue(0, 0), ^(size_t i) {
sleep(2);
NSLog(@"任務(wù)組%d完成%@",i,[NSThread currentThread]);
});
打印結(jié)果
2016-07-31 23:54:15.378 GCD[81633:8885238] 任務(wù)組0完成<NSThread: 0x7fbaf3704c10>{number = 1, name = main}
2016-07-31 23:54:15.381 GCD[81633:8885283] 任務(wù)組2完成<NSThread: 0x7fbaf3623960>{number = 2, name = (null)}
2016-07-31 23:54:15.381 GCD[81633:8885275] 任務(wù)組1完成<NSThread: 0x7fbaf3526f50>{number = 3, name = (null)}
dispatch_apply和for循環(huán)都是串行的米死,當(dāng)dispatch_apply中的工作全都完成后才會(huì)返回,可以看出來里面的是并發(fā)執(zhí)行的所以返回的先后順序是不能確定的鼎俘,不能和for循環(huán)一樣用哲身。
dispatch_suspend和dispatch_resume
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//提交第一個(gè)block,延時(shí)5秒打印贸伐。
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"After 5 seconds...");
});
//提交第二個(gè)block,也是延時(shí)5秒打印
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"After 5 seconds again...");
});
//延時(shí)一秒
NSLog(@"sleep 1 second...");
[NSThread sleepForTimeInterval:1];
//掛起隊(duì)列
NSLog(@"suspend...");
dispatch_suspend(queue);
//延時(shí)10秒
NSLog(@"sleep 10 second...");
[NSThread sleepForTimeInterval:10];
//恢復(fù)隊(duì)列
dispatch_resume(queue);
NSLog(@"resume...");
打印結(jié)果
2016-08-01 13:46:29.817 GCD[81925:8937362] sleep 1 second...
2016-08-01 13:46:30.818 GCD[81925:8937362] suspend...
2016-08-01 13:46:30.819 GCD[81925:8937362] sleep 10 second...
2016-08-01 13:46:34.819 GCD[81925:8937412] After 5 seconds...
2016-08-01 13:46:40.819 GCD[81925:8937362] resume...
2016-08-01 13:46:45.820 GCD[81925:8937412] After 5 seconds again...
這兩個(gè)就是將任務(wù)掛起和恢復(fù)的功能怔揩,從結(jié)果可以看出來當(dāng)隊(duì)列掛起后第一個(gè)block還是在運(yùn)行捉邢,并且能夠正常的輸出脯丝。可知伏伐,dispatch_suspend并不會(huì)停止正在運(yùn)行的block宠进,只會(huì)暫停后續(xù)的block的執(zhí)行。
dispatch_barrier_async和dispatch_barrier_sync
dispatch_queue_t queue = dispatch_queue_create("msdf", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(5);
NSLog(@"task1");
});
dispatch_async(queue, ^{
sleep(5);
NSLog(@"task2");
});
dispatch_barrier_async(queue, ^{
sleep(3);
NSLog(@"barrier");
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"task3");
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"task4");
});
dispatch_barrier_async的作用就是向某個(gè)隊(duì)列插入一個(gè)block藐翎,當(dāng)目前正在執(zhí)行的block運(yùn)行完成后材蹬,阻塞這個(gè)block后面添加的block,只運(yùn)行這個(gè)block直到完成吝镣,然后再繼續(xù)后續(xù)的任務(wù)堤器。
dispatch_barrier\(a)sync只在自己創(chuàng)建的并發(fā)隊(duì)列上有效,在全局(Global)并發(fā)隊(duì)列末贾、串行隊(duì)列上闸溃,效果跟dispatch_(a)sync效果一樣。
接下來就來看看dispatch_barrier_sync和dispatch_barrier_async的相同點(diǎn)與不同點(diǎn)
相同點(diǎn):兩者只有前面的任務(wù)執(zhí)行完成之后并且執(zhí)行完成barrier中的代碼后才能執(zhí)行后面的任務(wù)拱撵。
兩者都能夠阻塞當(dāng)前的線程
不同點(diǎn):dispatch_barrier_async在阻塞當(dāng)前線程時(shí)辉川,不需要等待barrier中的代碼完成后再運(yùn)行后面的非任務(wù)代碼;
dispatch_barrier_sync在阻塞當(dāng)前線程時(shí)拴测,需要等待barrier中的代碼完成后再運(yùn)行后面的非任務(wù)代碼乓旗。
dispatch_semaphore
信號(hào)量是一個(gè)整形值并且具有一個(gè)初始計(jì)數(shù)值,并且支持兩個(gè)操作:信號(hào)通知和等待集索。當(dāng)一個(gè)信號(hào)量被信號(hào)通知屿愚,其計(jì)數(shù)會(huì)被增加。當(dāng)一個(gè)線程在一個(gè)信號(hào)量上等待時(shí)抄谐,線程會(huì)被阻塞(如果有必要的話)渺鹦,直至計(jì)數(shù)器大于零,然后線程會(huì)減少這個(gè)計(jì)數(shù)蛹含。
在GCD中有三個(gè)函數(shù)是semaphore的操作毅厚,分別是:
dispatch_semaphore_create 創(chuàng)建一個(gè)semaphore
dispatch_semaphore_signal 發(fā)送一個(gè)信號(hào)
dispatch_semaphore_wait 等待信號(hào)
簡(jiǎn)單的介紹一下這三個(gè)函數(shù),第一個(gè)函數(shù)有一個(gè)整形的參數(shù)浦箱,我們可以理解為信號(hào)的總量吸耿,dispatch_semaphore_signal是發(fā)送一個(gè)信號(hào),自然會(huì)讓信號(hào)總量加1酷窥,dispatch_semaphore_wait等待信號(hào)咽安,當(dāng)信號(hào)總量少于0的時(shí)候就會(huì)一直等待,否則就可以正常的執(zhí)行蓬推,并讓信號(hào)總量-1妆棒,根據(jù)這樣的原理,我們便可以快速的創(chuàng)建一個(gè)并發(fā)控制來同步任務(wù)和有限資源訪問控制。
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
簡(jiǎn)單的介紹一下這一段代碼糕珊,創(chuàng)建了一個(gè)初使值為10的semaphore动分,每一次for循環(huán)都會(huì)創(chuàng)建一個(gè)新的線程,線程結(jié)束的時(shí)候會(huì)發(fā)送一個(gè)信號(hào)红选,線程創(chuàng)建之前會(huì)信號(hào)等待澜公,所以當(dāng)同時(shí)創(chuàng)建了10個(gè)線程之后,for循環(huán)就會(huì)阻塞喇肋,等待有線程結(jié)束之后會(huì)增加一個(gè)信號(hào)才繼續(xù)執(zhí)行坟乾,如此就形成了對(duì)并發(fā)的控制,如上就是一個(gè)并發(fā)數(shù)為10的一個(gè)線程隊(duì)列蝶防。
dispatch_get_current_queue
現(xiàn)在這個(gè)方法已經(jīng)被不建議使用了甚侣,但是就像在此強(qiáng)調(diào)一下,這個(gè)方法用不好會(huì)造成死鎖慧脱,在此不再贅余渺绒。