這篇會(huì)整理GCD常用的API
目錄
1擒悬、dispatch_after
2摊唇、dispatch_apply
3左医、dispatch_barrier_async
4、Dispatch Semaphore
5特姐、Dispatch Group
dispatch_after
看下面一段代碼:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"hey");
});
dispatch_time_t類型變量作為dispatch_after函數(shù)的第一個(gè)參數(shù)晶丘,可以由dispatch_time和dispatch_walltime函數(shù)創(chuàng)建。先來(lái)說(shuō)前者唐含。
dispatch_time函數(shù)接收兩個(gè)參數(shù)浅浮,它會(huì)返回從第一個(gè)參數(shù)指定的時(shí)間開(kāi)始,到第二個(gè)參數(shù)指定的以毫微秒為單位的時(shí)間間隔后的時(shí)間觉壶。比如上面代碼中就是從現(xiàn)在開(kāi)始脑题,3秒后的時(shí)間件缸。
dispatch_after會(huì)將任務(wù)在指定時(shí)間后加入到執(zhí)行隊(duì)列铜靶,n * NSEC_PER_SEC會(huì)得到一個(gè)單位是毫微秒的數(shù)值,要表達(dá)3秒需要寫成3 * NSEC_PER_SEC他炊,直接寫3是不行的争剿。如果需要表示毫秒,可以使用NSEC_PER_MSEC痊末,比如100毫秒寫成100 * NSEC_PER_MSEC蚕苇。
dispatch_walltime用于指定絕對(duì)時(shí)間,比如要指定時(shí)間為2011年11月11日11時(shí)11分11秒凿叠,可以使用如下方法做NSDate到dispatch_time_t的轉(zhuǎn)換涩笤。
+ (dispatch_time_t)getDispatchTimeByDate:(NSDate *)date{
NSTimeInterval interval;
CGFloat second, subsecond;
struct timespec time;
dispatch_time_t milestone;
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
milestone = dispatch_walltime(&time, 0);
return milestone;
}
關(guān)于dispatch_after方法的參數(shù):
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block) 參數(shù)1:執(zhí)行的時(shí)間。參數(shù)2:執(zhí)行的隊(duì)列盒件。參數(shù)3:任務(wù)蹬碧。
另外,因?yàn)閐ispatch_after并不是在指定時(shí)間后執(zhí)行炒刁,而是指定時(shí)間后加入隊(duì)列恩沽,所示任務(wù)執(zhí)行的具體時(shí)間未必會(huì)準(zhǔn)確按照time參數(shù)。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC);
int i = 1;
while (i < 1000000000) {
i++;
}
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"hey");
});
比如這段代碼翔始,因?yàn)檠h(huán)和log都在主線程執(zhí)行罗心,循環(huán)會(huì)執(zhí)行很久里伯,所以0.5秒后輸出語(yǔ)句并沒(méi)有執(zhí)行,只是加入到隊(duì)列中渤闷,等到循環(huán)結(jié)束才輸出“hey”疾瓮。
如果只是簡(jiǎn)單的使用可以直接使用系統(tǒng)提供的代碼塊來(lái)快速生成代碼。
dispatch_apply
dispatch_apply會(huì)按照給定的次數(shù)在指定隊(duì)列中重復(fù)執(zhí)行任務(wù)肤晓,注意它是dispatch_sync和Dispatch Group的關(guān)聯(lián)API爷贫,為什么要注意這個(gè)等下就會(huì)看到。
NSArray *arr = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"];
dispatch_apply(arr.count, dispatch_get_main_queue(), ^(size_t index) {
NSLog(@"---%@", arr[index]);
});
在主線程中運(yùn)行這段代碼补憾,會(huì)發(fā)現(xiàn)不會(huì)有任何輸出并且程序也會(huì)卡死漫萄,沒(méi)錯(cuò)死鎖了。dispatch_apply函數(shù)和dispatch_sync相同盈匾,都會(huì)等待任務(wù)執(zhí)行結(jié)束腾务,也就意味著dispatch_apply也是一個(gè)同步方法。在死鎖的判斷中削饵,可以把它理解為一個(gè)sync函數(shù)岩瘦,這樣就明白為什么這里會(huì)發(fā)生死鎖了(關(guān)于死鎖的問(wèn)題在GCD整理(一))。
另外如果dispatch_apply函數(shù)指定了串行隊(duì)列作為參數(shù)窿撬,那么遍歷會(huì)按照順序執(zhí)行启昧,如果是并發(fā)隊(duì)列,執(zhí)行順序則不受控制劈伴。因?yàn)榇嘘?duì)列只對(duì)應(yīng)一條線程密末,并發(fā)隊(duì)列會(huì)對(duì)應(yīng)多個(gè)線程。
代碼和效果如下:
NSArray *arr = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"];
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_apply(arr.count, queue, ^(size_t index) {
NSLog(@"---%@", arr[index]);
});
NSArray *arr = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"];
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(arr.count, queue, ^(size_t index) {
NSLog(@"---%@", arr[index]);
});
dispatch_barrier_async
提供這樣一種功能跛璧,它將dispatch_barrier_async函數(shù)之前加入隊(duì)列的任務(wù)和dispatch_barrier_async之后加入隊(duì)列的任務(wù)分割開(kāi)严里。并且dispatch_barrier_async的block會(huì)等待前面的任務(wù)執(zhí)行完成后在執(zhí)行,同時(shí)block執(zhí)行完成之前即使是并發(fā)隊(duì)列后面的任務(wù)也不會(huì)執(zhí)行追城。
一個(gè)典型的例子是使用dispatch_barrier_async處理讀寫問(wèn)題刹碾,在讀寫數(shù)據(jù)庫(kù)表的操作中,如果單純的使用dispatch_async函數(shù)執(zhí)行寫操作并且并發(fā)隊(duì)列中存在多個(gè)寫任務(wù)座柱,那么寫入的數(shù)據(jù)很有可能是錯(cuò)誤的迷帜。我們只希望同一時(shí)間只有一個(gè)任務(wù)在操作表。
另外一個(gè)情況色洞,當(dāng)任務(wù)1-2要執(zhí)行讀取戏锹,讀取后任務(wù)3要進(jìn)行寫入,寫入完成后任務(wù)4再次讀取拿到新寫入的數(shù)據(jù)锋玲。這樣的情況使用dispatch_barrier_async就會(huì)很方便景用。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"read1");
});
dispatch_async(queue, ^{
NSLog(@"read2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"write");
});
dispatch_async(queue, ^{
NSLog(@"read4");
});
Dispatch Semaphore
信號(hào)量的作用和dispatch_barrier_async有些類似,它可以更加細(xì)致的控制數(shù)據(jù)的線程安全性。
描述信號(hào)量的概念可以生動(dòng)的用廁所來(lái)比喻伞插,初始值為1的信號(hào)量就像只有一個(gè)坑的廁所割粮,初始值為2就是兩個(gè)坑的廁所。每當(dāng)進(jìn)去一個(gè)人就會(huì)把門鎖上媚污,信號(hào)量就減1舀瓢;出來(lái)的時(shí)候自然會(huì)把門打開(kāi),信號(hào)量就加1耗美。如果廁所所有的坑都被占了京髓,后面來(lái)的人就只能等待。
可以用下面的語(yǔ)法創(chuàng)建一個(gè)信號(hào)量:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_create接受一個(gè)長(zhǎng)整形參數(shù)商架,這段代碼就好比創(chuàng)建了一個(gè)只有一個(gè)坑的廁所堰怨。可以用下面的語(yǔ)句來(lái)描述一個(gè)人占據(jù)了一個(gè)坑位的情況蛇摸。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
dispatch_semaphore_wait(semaphore, time);
dispatch_semaphore_wait接受兩個(gè)參數(shù)备图,參數(shù)1:對(duì)應(yīng)的信號(hào)量。參數(shù)2:超時(shí)時(shí)間赶袄。這個(gè)函數(shù)有一個(gè)長(zhǎng)整形返回值揽涮,返回值為0表示信號(hào)量的值大于等于1,或者在指定的等待時(shí)間內(nèi)饿肺,超時(shí)返回非0蒋困。
引用一段別人對(duì)該函數(shù)的描述。
如果信號(hào)量的值大于0敬辣,該函數(shù)所處線程就繼續(xù)執(zhí)行下面的語(yǔ)句雪标,并且將信號(hào)量的值減1;如果信號(hào)量的值為0购岗,那么這個(gè)函數(shù)就阻塞當(dāng)前線程等待timeout汰聋,如果等待的期間信號(hào)量的值被dispatch_semaphore_signal函數(shù)加1了门粪,且該函數(shù)(即dispatch_semaphore_wait)所處線程獲得了信號(hào)量喊积,那么就繼續(xù)向下執(zhí)行并將信號(hào)量減1。如果等待期間沒(méi)有獲取到信號(hào)量或者信號(hào)量的值一直為0玄妈,那么等到timeout時(shí)乾吻,其所處線程自動(dòng)執(zhí)行其后語(yǔ)句。參考鏈接
有減就一定有加拟蜻,dispatch_semaphore_signal就是對(duì)應(yīng)從廁所開(kāi)門出來(lái)那段劇情的函數(shù)绎签。這個(gè)函數(shù)會(huì)把傳入的信號(hào)量加1,它同樣有返回值酝锅,0代表當(dāng)前并沒(méi)有等待中的線程需要被喚醒诡必;非0代表當(dāng)前有至少一個(gè)等待中的線程,并且成功喚醒了其中一個(gè)。
運(yùn)行一段代碼看下效果:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
for (NSInteger i = 0; i < 3; i++) {
dispatch_async(queue, ^{
NSInteger wait = dispatch_semaphore_wait(semaphore, time);
NSLog(@"wait:%ld-%@", wait, [NSThread currentThread]);
int i = 1;
while (i < 1000000000) {
i++;
}
NSLog(@"finish");
NSInteger singal = dispatch_semaphore_signal(semaphore);
NSLog(@"singal:%ld-%@", singal, [NSThread currentThread]);
});
}
分析代碼的運(yùn)行邏輯:number=2的線程最先拿到了信號(hào)量爸舒,并將信號(hào)量減1蟋字,與此同時(shí)3、4線程也運(yùn)行到了這里并且開(kāi)始等待扭勉。在等待過(guò)程中2線程將循環(huán)執(zhí)行完畢并調(diào)用dispatch_semaphore_signal函數(shù)鹊奖,dispatch_semaphore_signal發(fā)現(xiàn)還有兩個(gè)線程在等待中,有線程在等待就返回非0(這里為1)涂炎;同時(shí)線程3開(kāi)始拿到信號(hào)量并執(zhí)行循環(huán)忠聚。這時(shí)候指定的等待時(shí)間3秒已經(jīng)到了,線程4不能再等了唱捣,于是開(kāi)始執(zhí)行循環(huán)两蟀。這就意味著線程3和4在某一段時(shí)間內(nèi)是同時(shí)執(zhí)行循環(huán)操作的。隨后各自執(zhí)行dispatch_semaphore_signal函數(shù)震缭,此時(shí)已經(jīng)沒(méi)有等待線程存在所以返回0垫竞。
再放一個(gè)例子:
NSMutableArray *array = [[NSMutableArray alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (NSInteger i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:@(i)];
dispatch_semaphore_signal(semaphore);
});
}
因?yàn)镹SMutableArray是非線程安全的,如果同一時(shí)間有多個(gè)線程對(duì)array數(shù)組進(jìn)行addObject操作一定會(huì)發(fā)生異常蛀序』兜桑可以使用信號(hào)量來(lái)控制同一時(shí)間只有一條線程執(zhí)行addObject操作。
Dispatch Group
Dispatch Group 可以處理這樣一種情況:一個(gè)或多個(gè)并發(fā)隊(duì)列中有很多任務(wù)在執(zhí)行徐裸,而有一個(gè)任務(wù)必須在所有任務(wù)都執(zhí)行完成后最后執(zhí)行遣鼓。比如要上傳多張圖片到后臺(tái),在全部上傳完成后要告知用戶圖片上傳完成重贺。
這時(shí)Dispatch Group就會(huì)發(fā)揮極大的作用:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue1, ^{
NSLog(@"queue1 hello:%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue1, ^{
NSLog(@"queue1 world:%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue2, ^{
NSLog(@"queue2 hello:%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue2, ^{
NSLog(@"queue2 world:%@", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"finish:%@", [NSThread currentThread]);
});
dispatch_group_create函數(shù)用來(lái)創(chuàng)建一個(gè) Dispatch Group骑祟。dispatch_group_async用法與dispatch_async一樣,只是多了個(gè)group參數(shù)气笙。dispatch_group_notify函數(shù)會(huì)在group里的block全部執(zhí)行完成后運(yùn)行自己的block次企。
Dispatch Group還有一個(gè)很重要的方法:
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout); 指定Dispatch Group的等待時(shí)間。參數(shù)1:組潜圃。參數(shù)2:超時(shí)時(shí)間缸棵。函數(shù)有一個(gè)返回值,為0表示在指定時(shí)間內(nèi)所有的block都已經(jīng)執(zhí)行完畢谭期。非0表示在指定時(shí)間內(nèi)任務(wù)沒(méi)有執(zhí)行完堵第。
還有比較常見(jiàn)的Dispatch Once,用法比較簡(jiǎn)單也不整理了隧出。
最后踏志,這篇GCD整理是閱讀《Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理》GCD部分的內(nèi)容后,結(jié)合其他大大們的博文以及自己的理解整理的一份筆記胀瞪。水平有限针余,歡迎指正。