1枯夜、介紹
- 什么是GCD堵漱?
Grand Central Dispatch,是蘋(píng)果公司開(kāi)發(fā)的一套多核編程的底層API莱找。GCD首次發(fā)布在Mac OS X 10.6酬姆,iOS4及以上也可用。GCD存在于libdispatch.dylib這個(gè)庫(kù)中宋距,iOS程序默認(rèn)動(dòng)態(tài)加載這個(gè)庫(kù)轴踱,無(wú)需手動(dòng)引入。 - GCD工作原理
讓程序平行排隊(duì)的特定任務(wù)谚赎,根據(jù)可用的處理資源淫僻,安排他們?cè)谌魏慰捎玫奶幚砥骱诵纳蠄?zhí)行任務(wù)诱篷。一個(gè)任務(wù)可以是一個(gè)Function或是一個(gè)block。GCD的底層依然是用線程實(shí)現(xiàn)雳灵,不過(guò)這樣可以讓程序員不用關(guān)注實(shí)現(xiàn)的細(xì)節(jié)棕所。 - GCD優(yōu)勢(shì)
GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)悯辙。
GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出琳省,放到對(duì)應(yīng)的線程中執(zhí)行,任務(wù)的取出遵循隊(duì)列的FIFO原則躲撰。 - GCD核心概念
任務(wù)(block)和隊(duì)列(queue)针贬。
2、隊(duì)列(Dispatch Queue)
- GCD隊(duì)列可以分為兩大類型:串行隊(duì)列和并發(fā)隊(duì)列拢蛋。
串行隊(duì)列(Serial Dispatch Queue):同時(shí)只執(zhí)行一個(gè)任務(wù)桦他,通常用于同步訪問(wèn)特定的資源或數(shù)據(jù)。當(dāng)你創(chuàng)建多個(gè)串行隊(duì)列時(shí)谆棱,雖然它們各自是同步執(zhí)行的快压,但隊(duì)列之間是并發(fā)執(zhí)行的。
并發(fā)隊(duì)列(Concurrent Dispatch Queue):可以讓多個(gè)任務(wù)并發(fā)執(zhí)行(自動(dòng)開(kāi)啟多個(gè)線程同時(shí)執(zhí)行任務(wù)垃瞧,如果同時(shí)執(zhí)行10個(gè)任務(wù)蔫劣,那么10個(gè)任務(wù)并不是開(kāi)啟10個(gè)線程,線程會(huì)根據(jù)任務(wù)執(zhí)行情況復(fù)用个从,具體線程數(shù)由系統(tǒng)決定)脉幢,并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效,執(zhí)行完成的順序是隨機(jī)的信姓。 - GCD兩種獲取隊(duì)列的方式:手動(dòng)創(chuàng)建和獲取系統(tǒng)提供的鸵隧。
(1)dispatch_queue_create(手動(dòng)創(chuàng)建隊(duì)列)
【語(yǔ)法】
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
示例
- (void)dispatchQueueCreateTest
{
NSLog(@"A:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com.jinnchang.gcd", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"B:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"C:%@", [NSThread currentThread]);
});
NSLog(@"D:%@", [NSThread currentThread]);
}
// 打印結(jié)果:
// 2021-05-09 13:59:26.984 GCDDemo[3714:99120] A:{number = 1, name = main}
// 2021-05-09 13:59:26.984 GCDDemo[3714:99120] D:{number = 1, name = main}
// 2021-05-09 13:59:26.984 GCDDemo[3714:99215] B:{number = 2, name = (null)}
// 2021-05-09 13:59:26.985 GCDDemo[3714:99215] C:{number = 2, name = (null)}
【說(shuō)明】
dispatch_queue_create參數(shù)中的label是隊(duì)列名稱,一般使用倒序的全域名(雖然可以不給隊(duì)列指定一個(gè)名稱意推,但是有名稱的隊(duì)列可以讓我們?cè)谟龅絾?wèn)題時(shí)更好調(diào)試)豆瘫。attr為DISPATCH_QUEUE_SERIAL時(shí)返回串行隊(duì)列,為DISPATCH_QUEUE_CONCURRENT時(shí)返回并發(fā)隊(duì)列(如果填NULL默認(rèn)是DISPATCH_QUEUE_SERIAL)菊值。
(2)dispatch_get_main_queue(獲取系統(tǒng)主隊(duì)列)外驱、dispatch_get_global_queue(獲取系統(tǒng)全局并發(fā)隊(duì)列)
【語(yǔ)法】
dispatch_queue_t dispatch_get_main_queue(void);
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
示例
- (void)mainAndGlobalQueueTest
{
NSLog(@"A:%@", [NSThread currentThread]);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗時(shí)操作
NSLog(@"B:%@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面操作
NSLog(@"C:%@", [NSThread currentThread]);
});
});
NSLog(@"D:%@", [NSThread currentThread]);
}
// 打印結(jié)果:
// 2021-05-09 14:05:13.732 GCDDemo[3794:101596] A:{number = 1, name = main}
// 2021-05-09 14:05:13.733 GCDDemo[3794:101596] D:{number = 1, name = main}
// 2021-05-09 14:05:13.733 GCDDemo[3794:101653] B:{number = 2, name = (null)}
// 2021-05-09 14:05:13.764 GCDDemo[3794:101596] C:{number = 1, name = main}
【說(shuō)明】
主隊(duì)列是GCD自帶的一種特殊的串行隊(duì)列,放在主隊(duì)列中的任務(wù),都會(huì)放到主線程中執(zhí)行腻窒。主線程是唯一可用于更新UI的線程昵宇。
GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊(duì)列,供整個(gè)應(yīng)用使用儿子,不需要手動(dòng)創(chuàng)建瓦哎。
dispatch_get_global_queue參數(shù)中的identifier是優(yōu)先級(jí),有如下四種;flags這個(gè)參數(shù)是留給以后用的蒋譬,暫時(shí)用不上割岛,傳0即可。
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺(tái)
3犯助、調(diào)度任務(wù)
1癣漆、dispatch_async(用異步的方式執(zhí)行任務(wù))
【語(yǔ)法】
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
示例
- (void)dispatchAsyncTest
{
NSLog(@"A:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"B:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"C:%@", [NSThread currentThread]);
});
NSLog(@"D:%@", [NSThread currentThread]);
}
// 打印結(jié)果:
// 2021-05-09 14:18:43.040 GCDDemo[3965:106717] A:{number = 1, name = main}
// 2021-05-09 14:18:43.041 GCDDemo[3965:106717] D:{number = 1, name = main}
// 2021-05-09 14:18:43.041 GCDDemo[3965:106754] B:{number = 2, name = (null)}
// 2021-05-09 14:18:43.041 GCDDemo[3965:106755] C:{number = 3, name = (null)}
【說(shuō)明】
異步方式具備開(kāi)啟新線程的能力。會(huì)立即返回剂买,不會(huì)堵塞當(dāng)前線程惠爽。
2、dispatch_sync(用同步方式執(zhí)行任務(wù))
【語(yǔ)法】
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
示例
- (void)dispatchSyncTest
{
NSLog(@"A:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
NSLog(@"B:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"C:%@", [NSThread currentThread]);
});
NSLog(@"D:%@", [NSThread currentThread]);
}
// 打印結(jié)果:
// 2021-05-09 14:22:38.085 GCDDemo[4032:108410] A:{number = 1, name = main}
// 2021-05-09 14:22:38.085 GCDDemo[4032:108410] B:{number = 1, name = main}
// 2021-05-09 14:22:38.086 GCDDemo[4032:108410] C:{number = 1, name = main}
// 2021-05-09 14:22:38.086 GCDDemo[4032:108410] D:{number = 1, name = main}
【說(shuō)明】
同步方式不具備開(kāi)啟新線程的能力瞬哼。在堵塞在當(dāng)前線程中婚肆,等block執(zhí)行完成再返回。
3坐慰、dispatch_after(讓隊(duì)列任務(wù)延時(shí)執(zhí)行)
【語(yǔ)法】
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
示例
- (void)dispatchAfterTest
{
NSLog(@"A:%@", [NSThread currentThread]);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"B:%@", [NSThread currentThread]);
});
NSLog(@"C:%@", [NSThread currentThread]);
}
// 打印結(jié)果:
// 2021-05-09 14:29:01.119 GCDDemo[4198:111631] A:{number = 1, name = main}
// 2021-05-09 14:29:01.120 GCDDemo[4198:111631] C:{number = 1, name = main}
// 2021-05-09 14:29:02.185 GCDDemo[4198:111631] B:{number = 1, name = main}
【說(shuō)明】
dispatch_after的真正含義是在設(shè)定的時(shí)間后把任務(wù)添加進(jìn)隊(duì)列中旬痹,并不是表示在設(shè)定的時(shí)間后執(zhí)行,大部分情況該函數(shù)能達(dá)到我們的預(yù)期讨越,只有在對(duì)時(shí)間要求非常精準(zhǔn)的情況下才可能會(huì)出現(xiàn)問(wèn)題,這種情況下就要慎重使用了永毅。
NSEC_PER_SEC表示的是秒數(shù)把跨,它還提供了NSEC_PER_MSEC表示毫秒數(shù)。
4沼死、dispatch_apply
【語(yǔ)法】
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
示例
- (void)dispatchApplyTest
{
NSLog(@"A:%@", [NSThread currentThread]);
NSArray *array = @[@"B",@"C",@"D"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%@:%@", [array objectAtIndex:index], [NSThread currentThread]);
});
NSLog(@"E:%@", [NSThread currentThread]);
}
// 打印結(jié)果:
// 2021-05-09 14:33:34.729 GCDDemo[4306:113699] A:{number = 1, name = main}
// 2021-05-09 14:33:34.729 GCDDemo[4306:113721] C:{number = 3, name = (null)}
// 2021-05-09 14:33:34.729 GCDDemo[4306:113699] B:{number = 1, name = main}
// 2021-05-09 14:33:34.729 GCDDemo[4306:113722] D:{number = 2, name = (null)}
// 2021-05-09 14:33:34.730 GCDDemo[4306:113699] E:{number = 1, name = main}
【說(shuō)明】
如果要對(duì)某個(gè)數(shù)組中的所有元素執(zhí)行同樣的block的時(shí)候着逐,這個(gè)函數(shù)就顯得很有用了。并行運(yùn)算意蛀,然后等待所有運(yùn)算結(jié)束耸别。由于是并發(fā)隊(duì)列,不能保證哪個(gè)索引元素先執(zhí)行完县钥,但是dispatch_apply函數(shù)是同步的秀姐,執(zhí)行過(guò)程中會(huì)使線程在此處等待。如果需要整個(gè)操作異步執(zhí)行若贮,在dispatch_apply外再套一層dispatch_async可以實(shí)現(xiàn)省有。
5、dispatch_once
【語(yǔ)法】
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);
示例
+ (MyManager *)sharedInstance
{
static MyManager *sharedManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[MyManager alloc] init];
});
return sharedManager;
}
【說(shuō)明】
dispatch_once通常用在單例模式上谴麦,保證在程序運(yùn)行期間block只執(zhí)行一次蠢沿。
6、dispatch_group_async
【語(yǔ)法】
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
示例
- (void)dispatchGroupAsyncTest
{
NSLog(@"A:%@", [NSThread currentThread]);
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, ^{
NSLog(@"B:%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"C:%@", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"D:%@", [NSThread currentThread]);
});
NSLog(@"E:%@", [NSThread currentThread]);
}
// 打印結(jié)果:
// 2021-05-09 14:42:37.609 GCDDemo[4462:117434] A:{number = 1, name = main}
// 2021-05-09 14:42:37.610 GCDDemo[4462:117434] E:{number = 1, name = main}
// 2021-05-09 14:42:37.610 GCDDemo[4462:117480] B:{number = 3, name = (null)}
// 2021-05-09 14:42:37.610 GCDDemo[4462:117481] C:{number = 2, name = (null)}
// 2021-05-09 14:42:37.642 GCDDemo[4462:117434] D:{number = 1, name = main}
【說(shuō)明】
dispatch_group_async可以實(shí)現(xiàn)監(jiān)聽(tīng)一組任務(wù)是否完成匾效,完成后得到通知執(zhí)行其他的操作舷蟀。比如你執(zhí)行三個(gè)下載任務(wù),當(dāng)三個(gè)任務(wù)都下載完成后你才通知界面說(shuō)完成的了。除了使用dispatch_group_notify函數(shù)可以得到最后執(zhí)行完的通知外野宜,還可以使用dispatch_group_wait扫步。需要注意的是,dispatch_group_wait實(shí)際上會(huì)使當(dāng)前的線程處于等待的狀態(tài)速缨,也就是說(shuō)如果是在主線程執(zhí)行dispatch_group_wait锌妻,在Block執(zhí)行完之前,主線程會(huì)處于卡死的狀態(tài)旬牲。dispatch_group_wait的第二個(gè)參數(shù)是指定超時(shí)時(shí)間仿粹,如果指定為DISPATCH_TIME_FOREVER則表示會(huì)永久等待,直到Block全部執(zhí)行完原茅。
7吭历、dispatch_barrier_async
【語(yǔ)法】
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
示例
- (void)dispatchBarrierAsyncTest
{
NSLog(@"A:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com.jinnchang.gcd", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"B:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"C:%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"D:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"E:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"F:%@", [NSThread currentThread]);
});
NSLog(@"G:%@", [NSThread currentThread]);
}
// 打印結(jié)果:
// 2021-05-09 14:46:30.992 GCDDemo[4554:119327] A:{number = 1, name = main}
// 2021-05-09 14:46:30.993 GCDDemo[4554:119327] G:{number = 1, name = main}
// 2021-05-09 14:46:30.993 GCDDemo[4554:119367] C:{number = 2, name = (null)}
// 2021-05-09 14:46:30.993 GCDDemo[4554:119366] B:{number = 3, name = (null)}
// 2021-05-09 14:46:30.993 GCDDemo[4554:119366] D:{number = 3, name = (null)}
// 2021-05-09 14:46:30.993 GCDDemo[4554:119367] F:{number = 2, name = (null)}
// 2021-05-09 14:46:30.993 GCDDemo[4554:119366] E:{number = 3, name = (null)}
【說(shuō)明】
dispatch_barrier_async是在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會(huì)執(zhí)行.
如果上述示例中dispatch_queue_t是使用dispatch_get_global_queue創(chuàng)建的會(huì)發(fā)現(xiàn)執(zhí)行順序與預(yù)想的不一致擂橘,原因是dispatch_barrier_async的順序執(zhí)行仍然依賴queue的類型晌区,必須要求是dispatch_queue_create創(chuàng)建的,而且attr參數(shù)值必須是DISPATCH_QUEUE_CONCURRENT通贞。
4朗若、管理調(diào)度對(duì)象
dispatch_suspend、dispatch_resume
【語(yǔ)法】
void dispatch_suspend(dispatch_object_t object);
void dispatch_resume(dispatch_object_t object);
【說(shuō)明】
dispatch_suspend(暫停)和dispatch_resume(恢復(fù))在主線程上不起作用昌罩。
dispatch_suspend并不會(huì)立即暫停正在運(yùn)行的block哭懈,而是在當(dāng)前block執(zhí)行完成后,暫停后續(xù)的block執(zhí)行茎用。dispatch_release遣总、dispatch_retain
在MRC中需要釋放手動(dòng)創(chuàng)建的隊(duì)列,在ARC中系統(tǒng)自動(dòng)釋放轨功。通過(guò)dispatch_get_main_queue和dispatch_get_global_queue獲取的系統(tǒng)全局隊(duì)列旭斥,不用retain或release。
5古涧、信號(hào)量
【語(yǔ)法】
dispatch_semaphore_t dispatch_semaphore_create(long value);
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
示例
- (void)dispatchSemaphoreTest
{
NSLog(@"A:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSArray *array = @[@"B", @"C", @"D", @"E"];
for (int i = 0; i < [array count]; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[NSThread sleepForTimeInterval:1];
NSLog(@"%@:%@", [array objectAtIndex:i], [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
}
NSLog(@"F:%@", [NSThread currentThread]);
}
// 打印結(jié)果:
// 2021-05-09 14:50:54.840 GCDDemo[4675:121679] A:{number = 1, name = main}
// 2021-05-09 14:50:54.840 GCDDemo[4675:121679] F:{number = 1, name = main}
// 2021-05-09 14:50:55.842 GCDDemo[4675:121712] B:{number = 2, name = (null)}
// 2021-05-09 14:50:56.843 GCDDemo[4675:121711] C:{number = 3, name = (null)}
// 2021-05-09 14:50:57.845 GCDDemo[4675:121713] D:{number = 4, name = (null)}
// 2021-05-09 14:50:58.846 GCDDemo[4675:121718] E:{number = 5, name = (null)}
【說(shuō)明】
信號(hào)量在多線程開(kāi)發(fā)中被廣泛使用垂券,當(dāng)一個(gè)線程在進(jìn)入一段關(guān)鍵代碼之前,線程必須獲取一個(gè)信號(hào)量羡滑,一旦該關(guān)鍵代碼段完成了圆米,那么該線程必須釋放信號(hào)量。其它想進(jìn)入該關(guān)鍵代碼段的線程必須等待前面的線程釋放信號(hào)量啄栓。
信號(hào)量的具體做法是:當(dāng)信號(hào)計(jì)數(shù)大于0時(shí)娄帖,每條進(jìn)來(lái)的線程使計(jì)數(shù)減1,直到變?yōu)?昙楚,變?yōu)?后其他的線程將進(jìn)不來(lái)近速,處于等待狀態(tài);執(zhí)行完任務(wù)的線程釋放信號(hào),使計(jì)數(shù)加1削葱,如此循環(huán)下去奖亚。另外dispatch_semaphore_wait同樣也支持超時(shí),只需要給其第二個(gè)參數(shù)指定超時(shí)的時(shí)候析砸,然后通過(guò)返回值來(lái)判斷昔字。
信號(hào)量實(shí)現(xiàn)原理詳細(xì)解釋
6、相關(guān)概念解析
1首繁、死鎖
【示例】
- (void)deadLockTest
{
NSLog(@"A:%@", [NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"B:%@", [NSThread currentThread]);
});
NSLog(@"C:%@", [NSThread currentThread]);
}
【說(shuō)明】
上述代碼會(huì)發(fā)生死鎖作郭,因?yàn)橹骶€程通過(guò)dispatch_sync把block交給主隊(duì)列后,會(huì)等待block里的任務(wù)結(jié)束再往下走自身的任務(wù)弦疮,而隊(duì)列是先進(jìn)先出的夹攒,block里的任務(wù)也在等待主隊(duì)列當(dāng)中排在它之前的任務(wù)都執(zhí)行完了再走自己,這種循環(huán)等待就形成了死鎖胁塞。所以在主線程當(dāng)中使用dispatch_sync將任務(wù)加到主隊(duì)列是不可取的咏尝。