GCD 簡(jiǎn)介
1胡陪、什么是GCD?
全稱(chēng)是 Grand Central Dispatch碍舍,純 C 語(yǔ)言編寫(xiě)柠座,提供了非常多強(qiáng)大的函數(shù)
2、GCD的優(yōu)勢(shì)是什么片橡?
GCD 是蘋(píng)果公司為多核的并行運(yùn)算提出的解決方案
GCD 會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核妈经、四核)
GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷(xiāo)毀線程)
程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù)吹泡,不需要編寫(xiě)任何線程管理代碼
串行并行录煤、同步異步
一、概念
同步執(zhí)行(sync):在當(dāng)前線程中執(zhí)行任務(wù)荞胡,任務(wù)未執(zhí)行完時(shí)妈踊,會(huì)阻塞線程,不會(huì)開(kāi)辟線程。
異步執(zhí)行(async):另開(kāi)辟線程執(zhí)行任務(wù)泪漂,不會(huì)阻塞當(dāng)前線程廊营。
串行隊(duì)列:按順序一個(gè)一個(gè)執(zhí)行,FIFO先進(jìn)先出原則
并行隊(duì)列:可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行。(可以開(kāi)啟多個(gè)線程萝勤,并且同時(shí)執(zhí)行任務(wù))
一般使用中會(huì)有如下四種情況
- 串行隊(duì)列,同步執(zhí)行:在當(dāng)前線程中,一個(gè)個(gè)執(zhí)行
- 串行隊(duì)列,異步執(zhí)行:另開(kāi)辟一個(gè)線程,一個(gè)個(gè)執(zhí)行
- 并行隊(duì)列,同步執(zhí)行:在當(dāng)前線程中,一個(gè)個(gè)執(zhí)行
- 并行隊(duì)列,異步執(zhí)行:另開(kāi)辟多個(gè)線程,同時(shí)執(zhí)行
也就是說(shuō)串行同步和并行同步作用是一樣的露筒。
二、實(shí)例效果
結(jié)合代碼看一下效果
串行同步
/**
串行隊(duì)列,同步執(zhí)行
*/
- (void)createSerialQueueWithSync {
dispatch_queue_t serialQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"A==%@", [NSThread currentThread]);
dispatch_sync(serialQueue, ^{
NSLog(@"B==%@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"C==%@", [NSThread currentThread]);
});
NSLog(@"D==%@", [NSThread currentThread]);
}
串行同步打印結(jié)果如下:
從結(jié)果中我們能看到敌卓,符合串行隊(duì)列的特征(一個(gè)一個(gè)執(zhí)行)慎式,符合同步執(zhí)行特征(在當(dāng)前線程執(zhí)行,并且阻塞了當(dāng)前線程)趟径。
串行異步
/**
串行隊(duì)列,異步執(zhí)行
*/
- (void)createSerialQueueWithAsync {
dispatch_queue_t serialQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"A==%@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"B==%@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"C==%@", [NSThread currentThread]);
});
NSLog(@"D==%@", [NSThread currentThread]);
}
串行異步打印結(jié)果如下
從結(jié)果中可以看到瘪吏,符合串行隊(duì)列的特征(一個(gè)一個(gè)執(zhí)行),符合異步執(zhí)行特征(另開(kāi)辟線程蜗巧,且不阻塞當(dāng)前線程)掌眠。
并行同步
/**
并行隊(duì)列,同步執(zhí)行
*/
- (void)createConcurrentQueueWithSync {
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t conCurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//這個(gè)是全局并行隊(duì)列,一般并行任務(wù)都會(huì)加到這里面去
dispatch_sync(conCurrentQueue, ^{
NSLog(@"A==%@", [NSThread currentThread]);
});
dispatch_sync(conCurrentQueue, ^{
NSLog(@"B==%@", [NSThread currentThread]);
});
dispatch_sync(conCurrentQueue, ^{
NSLog(@"C==%@", [NSThread currentThread]);
});
NSLog(@"D==%@", [NSThread currentThread]);
}
并行同步打印結(jié)果如下
從結(jié)果可以看出,符合同步特征(在當(dāng)前線程幕屹,且阻塞當(dāng)前線程)蓝丙,但是并不清楚是否符合并行隊(duì)列的特征,并行隊(duì)列的效果需要異步執(zhí)行才能看出來(lái)望拖,并行同步整體的效果和串行同步相同渺尘。
并行異步
/**
并行隊(duì)列,異步執(zhí)行
*/
- (void)createConcurrentQueueWithAsync {
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conCurrentQueue, ^{
NSLog(@"A==%@", [NSThread currentThread]);
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"B==%@", [NSThread currentThread]);
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"C==%@", [NSThread currentThread]);
});
NSLog(@"D==%@", [NSThread currentThread]);
}
并行異步打印結(jié)果如下
從結(jié)果中可以看出,符合并行隊(duì)列特征(可以并發(fā)執(zhí)行線程任務(wù))说敏,符合異步執(zhí)行特征(另開(kāi)辟線程鸥跟,且不阻塞當(dāng)前線程)。
//全局并發(fā)隊(duì)列
dispatch_get_global_queue
//主線程--串行隊(duì)列
dispatch_get_main_queue()
這兩個(gè)隊(duì)列在系統(tǒng)剛啟動(dòng)時(shí)就創(chuàng)建好的隊(duì)列像云,一般不建議使用全局并發(fā)隊(duì)列锌雀,因?yàn)樵谌植l(fā)隊(duì)列中也會(huì)執(zhí)行系統(tǒng)的任務(wù)蚂夕,對(duì)于調(diào)試和業(yè)務(wù)剝離等造成影響迅诬。
死鎖
死鎖總結(jié)一句話:串行隊(duì)列
(當(dāng)然也包括主隊(duì)列
)中向該隊(duì)列添加同步
任務(wù),必定導(dǎo)致死鎖婿牍。
兩個(gè)死鎖案例幫助理解這句話侈贷,
例 1:
- (void)test1 {
//1,5,2,造成死鎖,
//1俏蛮,5撑蚌,2,async是異步搏屑,所以會(huì)開(kāi)辟線程争涌,當(dāng)然開(kāi)辟線程需要耗時(shí),所以5在先辣恋,2在后
//為什么會(huì)造成死鎖亮垫?因?yàn)殛?duì)列中加入任務(wù)的順序是2、4伟骨、3饮潦,按照隊(duì)列的FIFO原則,理應(yīng)4執(zhí)行完再執(zhí)行3携狭,但執(zhí)行完2遇到了sync同步函數(shù)继蜡,需要阻塞線程,4需要等待3執(zhí)行完再執(zhí)行逛腿,造成了互相等待即死鎖稀并。
NSLog(@"1==%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"2==%@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"3==%@", [NSThread currentThread]);
});
NSLog(@"4==%@", [NSThread currentThread]);
});
NSLog(@"5==%@", [NSThread currentThread]);
}
輸出結(jié)果是 1、5单默、2稻轨,然后發(fā)生死鎖崩潰,執(zhí)行過(guò)程如下
- 主隊(duì)列按順序先執(zhí)行 test 代碼塊簡(jiǎn)稱(chēng) A雕凹,由于
dispatch_async(queue
是異步串行的另一個(gè)隊(duì)列殴俱,不阻塞當(dāng)前隊(duì)列,所以這里先執(zhí)行了 A 的 1 和 5枚抵。 - 來(lái)到
dispatch_async(queue
代碼塊线欲,另起了新的線程處理隊(duì)列 queue,已知 queue 里面是(2汽摹、dispatch_sync(queue
李丰、4)代碼塊簡(jiǎn)稱(chēng) B。 - 執(zhí)行 B 的 2
- 執(zhí)行
dispatch_sync(queue
時(shí)發(fā)現(xiàn)這是同步函數(shù)逼泣,需要阻塞隊(duì)列 queue趴泌,在當(dāng)前線程執(zhí)行dispatch_sync(queue
代碼塊,但這個(gè)代碼塊的要在 queue 中執(zhí)行拉庶,而 queue 已經(jīng)被阻塞了嗜憔,造成了互相等待,導(dǎo)致死鎖氏仗。
例 2:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"deadlock");
});
}
同步對(duì)于任務(wù)是立即執(zhí)行的吉捶,當(dāng)把任務(wù)放進(jìn)主隊(duì)列時(shí),它會(huì)立即執(zhí)行,只有執(zhí)行完這個(gè)任務(wù)呐舔,viewDidLoad
才會(huì)繼續(xù)執(zhí)行币励。
而viewDidLoad
和任務(wù)都在主隊(duì)列上,根據(jù)隊(duì)列的先入先出原則珊拼,需要先執(zhí)行完viewDidLoad
再執(zhí)行任務(wù)食呻,造成了互相等待,導(dǎo)致死鎖澎现。
柵欄函數(shù)barrier
一搁进、概念
在dispatch_barrier_async
加入到自定義的并行隊(duì)列(不能是全局global隊(duì)列)時(shí),程序會(huì)先執(zhí)行隊(duì)列中barrier之前的任務(wù),再執(zhí)行barrier的任務(wù),最后再執(zhí)行隊(duì)列中barrier之后的任務(wù)。
二昔头、實(shí)例
直接上代碼
實(shí)例一饼问、應(yīng)用了dispatch_barrier_async
的隊(duì)列中,線程的執(zhí)行順序揭斧。
- (void)createBarrierAsync {
dispatch_queue_t queue = dispatch_queue_create("liufeng", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"start==%@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"A==%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"B==%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier_async==%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"C==%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"D==%@", [NSThread currentThread]);
});
NSLog(@"end==%@", [NSThread currentThread]);
}
控制臺(tái)打印效果如下
從打印結(jié)果我們可以看到莱革,程序會(huì)先執(zhí)行隊(duì)列中barrier之前的任務(wù),再執(zhí)行barrier的任務(wù),最后再執(zhí)行隊(duì)列中barrier之后的任務(wù)。
實(shí)例二讹开、從網(wǎng)絡(luò)加載圖片盅视,并給圖片加上水印后顯示到屏幕上
- (void)testPic {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.waterImage", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSString *logoStr = @"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3351002169,4211425181&fm=27&gp=0.jpg";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
UIImage *image = [UIImage imageWithData:data];
[self.mArray addObject:image];
});
dispatch_async(concurrentQueue, ^{
NSString *logoStr = @"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3033952616,135704646&fm=27&gp=0.jpg";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
UIImage *image = [UIImage imageWithData:data];
[self.mArray addObject:image];
});
__block UIImage *newImage = nil;
dispatch_barrier_async(concurrentQueue, ^{
for (int i = 0; i<self.mArray.count; i++) {
UIImage *waterImage = self.mArray[i];
newImage = [LFPrintImageTool printText:@"小姑娘還不睡呀?" onImage:waterImage];
}
});
dispatch_async(concurrentQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = newImage;
});
});
}
模擬器顯示如圖所示
這個(gè)從結(jié)果看不出來(lái)什么旦万,主要看代碼闹击,我們程序的步驟是這樣的,先將圖片從網(wǎng)絡(luò)獲取下來(lái)成艘,才能給它加水印赏半,最后顯示到屏幕上,柵欄函數(shù)相當(dāng)于阻塞了當(dāng)前線程淆两。
注意:柵欄函數(shù)必須是自定義的并發(fā)隊(duì)列才有效断箫,且必須是同一隊(duì)列中的線程才有效。
調(diào)度組
一秋冰、概念
在調(diào)度組內(nèi)的線程都執(zhí)行完畢后仲义,dispatch_notify
函數(shù)會(huì)觸發(fā)回調(diào)。
二剑勾、實(shí)例
應(yīng)用場(chǎng)景:
一個(gè)業(yè)務(wù)需要開(kāi)啟N個(gè)異步線程埃撵,但是后續(xù)操作,需要依賴(lài)N個(gè)線程返回的數(shù)據(jù)完成操作虽另。
直接上代碼
實(shí)例一
- (void)createGroupQueue {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"A---%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"B---%@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
NSLog(@"C---%@", [NSThread currentThread]);
});
dispatch_notify(group, queue, ^{
NSLog(@"隊(duì)列組任務(wù)執(zhí)行完畢");
});
}
調(diào)度組執(zhí)行結(jié)果如下
從結(jié)果中可以看出暂刘,符合預(yù)期,調(diào)度組內(nèi)的線程都執(zhí)行完畢后洲赵,
dispatch_notify
函數(shù)會(huì)觸發(fā)回調(diào)鸳惯。
實(shí)例二
- (void)test2 {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"A---%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"B---%@", [NSThread currentThread]);
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"C---%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_notify(group, queue, ^{
NSLog(@"隊(duì)列組任務(wù)執(zhí)行完畢");
});
}
上面這個(gè)實(shí)例是以進(jìn)組出組的方式實(shí)現(xiàn)調(diào)度。
dispatch_group_enter(group);
dispatch_group_leave(group);
進(jìn)組出組的方式類(lèi)似于信號(hào)量叠萍,內(nèi)部有一個(gè)signal芝发,enter加1,leave減1苛谷,它們總是成對(duì)出現(xiàn)辅鲸,當(dāng)signal為0時(shí),表示調(diào)度組里面的任務(wù)都執(zhí)行完了腹殿。
信號(hào)量
一独悴、作用
可以控制并發(fā)隊(duì)列的最大并發(fā)數(shù),當(dāng)創(chuàng)建的信號(hào)量限制為1時(shí)锣尉,可以達(dá)到鎖的效果刻炒。
二、概念
信號(hào)量,一般用來(lái)線程并發(fā)數(shù)量自沧,信號(hào)量為幾坟奥,線程最大并發(fā)數(shù)就是幾
//創(chuàng)建信號(hào)量,參數(shù):信號(hào)量的初值拇厢,當(dāng)信號(hào)量小于0時(shí)阻塞當(dāng)前線程
dispatch_semaphore_create(信號(hào)量值)
//等待降低信號(hào)量
dispatch_semaphore_wait(信號(hào)量爱谁,等待時(shí)間)
//提高信號(hào)量
dispatch_semaphore_signal(信號(hào)量)
三、實(shí)例
- (void)createSemaphoreQueue {
//create的value表示孝偎,最多幾個(gè)資源可訪問(wèn)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任務(wù)1
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
//任務(wù)2
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
//任務(wù)3
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
}
控制臺(tái)打印效果如下
再次執(zhí)行效果如下
可以看到访敌,始終都是task1和task2先執(zhí)行完,信號(hào)量經(jīng)過(guò)dispatch_semaphore_signal
增加以后衣盾,task3才能執(zhí)行寺旺,結(jié)論就是不論并發(fā)隊(duì)列中有多少任務(wù)等待執(zhí)行,同一時(shí)間只允許兩個(gè)任務(wù)執(zhí)行势决。
總結(jié)
以上就是GCD的基本使用迅涮,在很多大型的底層框架中,很多使用的都是NSOperation
徽龟,因?yàn)?code>NSOperation更加靈活叮姑,開(kāi)發(fā)者可以更好的監(jiān)控線程的生命周期以及做處理。