原文鏈接:蘋果核
相信讀者已經(jīng)看過很多大神們對(duì)GCD深入淺出的分析,這也是老生常談的一個(gè)多線程的實(shí)現(xiàn)方式了,所以我也就不再啰嗦其理論。但是到底有多少方法是我們?nèi)粘>幊讨谐S玫??又有多少是你不知道的?今天,我就來例舉一些GCD的方法,絕對(duì)讓你看一眼就會(huì)正確得使用。
與其說CGD是線程管理,不如說是隊(duì)列管理,那么我們先來介紹一下GCD中常用的隊(duì)列:
Serial Diapatch Queue 串行隊(duì)列
當(dāng)任務(wù)相互依賴,具有明顯的先后順序的時(shí)候,使用串行隊(duì)列是一個(gè)不錯(cuò)的選擇
創(chuàng)建一個(gè)串行隊(duì)列:
dispatch_queue_t serialDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL);
第一個(gè)參數(shù)為隊(duì)列名,第二個(gè)參數(shù)為隊(duì)列類型,當(dāng)然,第二個(gè)參數(shù)人如果寫NULL,創(chuàng)建出來的也是一個(gè)串行隊(duì)列。然后我們?cè)诋惒骄€程來執(zhí)行這個(gè)隊(duì)列:
dispatch_async(serialDiapatchQueue, ^{
NSLog(@"1");
});
dispatch_async(serialDiapatchQueue, ^{
sleep(2);
NSLog(@"2");
});
dispatch_async(serialDiapatchQueue, ^{
sleep(1);
NSLog(@"3");
});
為了能更好的理解,我給每個(gè)異步線程都添加了一個(gè)log,看一下日志平臺(tái)的log:
2016-03-07 10:17:13.907 GCD[2195:61497] 1
2016-03-07 10:17:15.911 GCD[2195:61497] 2
2016-03-07 10:17:16.912 GCD[2195:61497] 3
沒錯(cuò),他在61497這個(gè)編號(hào)的線程中做了串行輸出,相互彼此依賴,串行執(zhí)行
Concurrent Diapatch Queue 并發(fā)隊(duì)列
與串行隊(duì)列剛好相反,他不會(huì)存在任務(wù)間的相互依賴。
創(chuàng)建一個(gè)并發(fā)隊(duì)列:
dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
比較2個(gè)隊(duì)列的創(chuàng)建,我們發(fā)現(xiàn)只有第二個(gè)參數(shù)從DISPATCH_QUEUE_SERIAL變成了對(duì)應(yīng)的DISPATCH_QUEUE_CONCURRENT,其他完全一樣。
用同一段代碼,換一種隊(duì)列我們來比較一下效果:
dispatch_async(concurrentDiapatchQueue, ^{
NSLog(@"1");
});
dispatch_async(concurrentDiapatchQueue, ^{
sleep(2);
NSLog(@"2");
});
dispatch_async(concurrentDiapatchQueue, ^{
sleep(1);
NSLog(@"3");
});
輸出的log:
2016-03-07 10:42:38.289 GCD[2260:72557] 1
2016-03-07 10:42:39.291 GCD[2260:72559] 3
2016-03-07 10:42:40.293 GCD[2260:72556] 2
我們發(fā)現(xiàn),log的輸出在3個(gè)不同編號(hào)的線程中進(jìn)行,而且相互不依賴,不阻塞。
Global Queue & Main Queue
這是系統(tǒng)為我們準(zhǔn)備的2個(gè)隊(duì)列:
- Global Queue其實(shí)就是系統(tǒng)創(chuàng)建的Concurrent Diapatch Queue
- Main Queue 其實(shí)就是系統(tǒng)創(chuàng)建的位于主線程的Serial Diapatch Queue
通常情況我們會(huì)把這2個(gè)隊(duì)列放在一起使用,也是我們最常用的開異步線程-執(zhí)行異步任務(wù)-回主線程的一種方式:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"異步線程");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"異步主線程");
});
});
通過上面的代碼我們發(fā)現(xiàn)了2個(gè)有意思的點(diǎn):
-
dispatch_get_global_queue存在優(yōu)先級(jí),沒錯(cuò),他一共有4個(gè)優(yōu)先級(jí):
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSLog(@"4");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSLog(@"3");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"1");
});
在指定優(yōu)先級(jí)之后,同一個(gè)隊(duì)列會(huì)按照這個(gè)優(yōu)先級(jí)執(zhí)行,打印的順序?yàn)?、2、3、4,當(dāng)然這不是串行隊(duì)列,所以不存在絕對(duì)回調(diào)先后。
-
異步主線程
- 在日常工作中,除了在其他線程返回主線程的時(shí)候需要用這個(gè)方法,還有一些時(shí)候我們?cè)谥骶€程中直接調(diào)用異步主線程,這是利用dispatch_async的特性:block中的任務(wù)會(huì)放在主線程本次runloop之后返回。這樣,有些存在先后順序的問題就可以得到解決了。
說完了隊(duì)列,我們?cè)僬f說GCD提供的一些操作隊(duì)列的方法
dispatch_set_target_queue
剛剛我們說了系統(tǒng)的Global Queue是可以指定優(yōu)先級(jí)的,那我們?nèi)绾谓o自己創(chuàng)建的隊(duì)列執(zhí)行優(yōu)先級(jí)呢?這里我們就可以用到dispatch_set_target_queue這個(gè)方法:
dispatch_queue_t serialDiapatchQueue=dispatch_queue_create("com.test.queue", NULL);
dispatch_queue_t dispatchgetglobalqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_set_target_queue(serialDiapatchQueue, dispatchgetglobalqueue);
dispatch_async(serialDiapatchQueue, ^{
NSLog(@"我優(yōu)先級(jí)低,先讓讓");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"我優(yōu)先級(jí)高,我先block");
});
我把自己創(chuàng)建的隊(duì)列塞到了系統(tǒng)提供的global_queue隊(duì)列中,我們可以理解為:我們自己創(chuàng)建的queue其實(shí)是位于global_queue中執(zhí)行,所以改變global_queue的優(yōu)先級(jí),也就改變了我們自己所創(chuàng)建的queue的優(yōu)先級(jí)。所以我們常用這種方式來管理子隊(duì)列。
dispatch_after
這個(gè)是最常用的,用來延遲執(zhí)行的GCD方法,因?yàn)樵谥骶€程中我們不能用sleep來延遲方法的調(diào)用,所以用它是最合適的,我們做一個(gè)簡單的例子:
NSLog(@"小破孩-波波1");
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(@"小破孩-波波2");
});
輸出的結(jié)果:
2016-03-07 11:25:06.019 GCD[2443:95722] 小破孩-波波1
2016-03-07 11:25:08.019 GCD[2443:95722] 小破孩-波波2
我們看到他就是在主線程,就是剛好延遲了2秒,當(dāng)然,我說這個(gè)2秒并不是絕對(duì)的,為什么這么說?還記得我之前在介紹dispatch_async這個(gè)特性的時(shí)候提到的嗎?他的block中方法的執(zhí)行會(huì)放在主線程runloop之后,所以,如果此時(shí)runloop周期較長的時(shí)候,可能會(huì)有一些時(shí)差產(chǎn)生。
dispatch_group
當(dāng)我們需要監(jiān)聽一個(gè)并發(fā)隊(duì)列中,所有任務(wù)都完成了,就可以用到這個(gè)group,因?yàn)椴l(fā)隊(duì)列你并不知道哪一個(gè)是最后執(zhí)行的,所以以單獨(dú)一個(gè)任務(wù)是無法監(jiān)聽到這個(gè)點(diǎn)的,如果把這些單任務(wù)都放到同一個(gè)group,那么,我們就能通過dispatch_group_notify方法知道什么時(shí)候這些任務(wù)全部執(zhí)行完成了。
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(@"0");});
dispatch_group_async(group, queue, ^{NSLog(@"1");});
dispatch_group_async(group, queue, ^{NSLog(@"2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"down");});
在例子中,我把3個(gè)log分別放在并發(fā)隊(duì)列中,通過把這個(gè)并發(fā)隊(duì)列任務(wù)統(tǒng)一加入group中,group每次runloop的時(shí)候都會(huì)調(diào)用一個(gè)方法dispatch_group_wait(group, DISPATCH_TIME_NOW),用來檢查group中的任務(wù)是否已經(jīng)完成,如果已經(jīng)完成了,那么會(huì)執(zhí)行dispatch_group_notify的block,輸出'down'看一下運(yùn)行結(jié)果:
2016-03-07 14:21:58.647 GCD[9424:156388] 2
2016-03-07 14:21:58.647 GCD[9424:156382] 0
2016-03-07 14:21:58.647 GCD[9424:156385] 1
2016-03-07 14:21:58.650 GCD[9424:156324] down
dispatch_barrier_async
此方法的作用是在并發(fā)隊(duì)列中,完成在它之前提交到隊(duì)列中的任務(wù)后打斷,單獨(dú)執(zhí)行其block,并在執(zhí)行完成之后才能繼續(xù)執(zhí)行在他之后提交到隊(duì)列中的任務(wù):
dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"0");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"1");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"2");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"3");});
dispatch_barrier_async(concurrentDiapatchQueue, ^{sleep(1); NSLog(@"4");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"5");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"6");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"7");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"8");});
輸出的結(jié)果為:
2016-03-07 14:45:32.410 GCD[10079:169655] 1
2016-03-07 14:45:32.410 GCD[10079:169658] 2
2016-03-07 14:45:32.410 GCD[10079:169656] 0
2016-03-07 14:45:32.410 GCD[10079:169661] 3
2016-03-07 14:45:33.414 GCD[10079:169661] 4
2016-03-07 14:45:33.415 GCD[10079:169661] 5
2016-03-07 14:45:33.415 GCD[10079:169658] 6
2016-03-07 14:45:33.415 GCD[10079:169655] 8
2016-03-07 14:45:33.415 GCD[10079:169662] 7
4之后的任務(wù)在我線程sleep之后才執(zhí)行,這其實(shí)就起到了一個(gè)線程鎖的作用,在多個(gè)線程同時(shí)操作一個(gè)對(duì)象的時(shí)候,讀可以放在并發(fā)進(jìn)行,當(dāng)寫的時(shí)候,我們就可以用dispatch_barrier_async方法,效果杠杠的。
dispatch_sync
-
dispatch_sync會(huì)在當(dāng)前線程執(zhí)行隊(duì)列,并且阻塞當(dāng)前線程中之后運(yùn)行的代碼,所以,同步線程非常有可能導(dǎo)致死鎖現(xiàn)象,我們這邊就舉一個(gè)死鎖的例子,直接在主線程調(diào)用以下代碼:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"有沒有同步主線程?");
});
根據(jù)FIFO(先進(jìn)先出)的原則,block里面的代碼應(yīng)該在主線程此次runloop后執(zhí)行,但是猶豫他是同步隊(duì)列,所有他之后的代碼會(huì)等待其執(zhí)行完成后才能繼續(xù)執(zhí)行,2者相互等待,所以就出現(xiàn)了死鎖。
我們?cè)倥e一個(gè)比較特殊的例子:
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{sleep(1);NSLog(@"1");});
dispatch_sync(queue, ^{sleep(1);NSLog(@"2");});
dispatch_sync(queue, ^{sleep(1);NSLog(@"3");});
NSLog(@"4");
其打印結(jié)果為:
2016-03-07 17:15:48.124 GCD[14198:272683] 1
2016-03-07 17:15:49.125 GCD[14198:272683] 2
2016-03-07 17:15:50.126 GCD[14198:272683] 3
2016-03-07 17:15:50.126 GCD[14198:272683] 4
從線程編號(hào)中我們發(fā)現(xiàn),同步方法沒有去開新的線程,而是在當(dāng)前線程中執(zhí)行隊(duì)列,會(huì)有人問,上文說dispatch_get_global_queue不是并發(fā)隊(duì)列,并發(fā)隊(duì)列不是應(yīng)該會(huì)在開啟多個(gè)線程嗎?這個(gè)前提是用異步方法。GCD其實(shí)是弱化了線程的管理,強(qiáng)化了隊(duì)列管理,這使我們理解變得比較形象。
dispatch_apply
這個(gè)方法用于無序查找,在一個(gè)數(shù)組中,我們能開啟多個(gè)線程來查找所需要的值,我這邊也舉個(gè)例子:
NSArray *array=[[NSArray alloc]initWithObjects:@"0",@"1",@"2",@"3",@"4",@"5",@"6", nil];
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu=%@",index,[array objectAtIndex:index]);
});
NSLog(@"阻塞");
輸出結(jié)果:
2016-03-07 17:36:50.726 GCD[14318:291701] 1=1
2016-03-07 17:36:50.726 GCD[14318:291705] 0=0
2016-03-07 17:36:50.726 GCD[14318:291783] 3=3
2016-03-07 17:36:50.726 GCD[14318:291782] 2=2
2016-03-07 17:36:50.726 GCD[14318:291784] 5=5
2016-03-07 17:36:50.726 GCD[14318:291627] 4=4
2016-03-07 17:36:50.726 GCD[14318:291785] 6=6
2016-03-07 17:36:50.727 GCD[14318:291627] 阻塞
通過輸出log,我們發(fā)現(xiàn)這個(gè)方法雖然會(huì)開啟多個(gè)線程來遍歷這個(gè)數(shù)組,但是在遍歷完成之前會(huì)阻塞主線程。
dispatch_suspend & dispatch_resume
隊(duì)列掛起和恢復(fù),這個(gè)沒什么好說的,直接上代碼:
dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentDiapatchQueue, ^{
for (int i=0; i<100; i++)
{
NSLog(@"%i",i);
if (i==50)
{
NSLog(@"-----------------------------------");
dispatch_suspend(concurrentDiapatchQueue);
sleep(3);
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_resume(concurrentDiapatchQueue);
});
}
}
});
我們甚至可以在不同的線程對(duì)這個(gè)隊(duì)列進(jìn)行掛起和恢復(fù),因?yàn)镚CD是對(duì)隊(duì)列的管理。
Semaphore
我們可以通過設(shè)置信號(hào)量的大小,來解決并發(fā)過多導(dǎo)致資源吃緊的情況,以單核CPU做并發(fā)為例,一個(gè)CPU永遠(yuǎn)只能干一件事情,那如何同時(shí)處理多個(gè)事件呢,聰明的內(nèi)核工程師讓CPU干第一件事情,一定時(shí)間后停下來,存取進(jìn)度,干第二件事情以此類推,所以如果開啟非常多的線程,單核CPU會(huì)變得非常吃力,即使多核CPU,核心數(shù)也是有限的,所以合理分配線程,變得至關(guān)重要,那么如何發(fā)揮多核CPU的性能呢?如果讓一個(gè)核心模擬傳很多線程,經(jīng)常干一半放下干另一件事情,那效率也會(huì)變低,所以我們要合理安排,將單一任務(wù)或者一組相關(guān)任務(wù)并發(fā)至全局隊(duì)列中運(yùn)算或者將多個(gè)不相關(guān)的任務(wù)或者關(guān)聯(lián)不緊密的任務(wù)并發(fā)至用戶隊(duì)列中運(yùn)算,所以用好信號(hào)量,合理分配CPU資源,程序也能得到優(yōu)化,當(dāng)日常使用中,信號(hào)量也許我們只起到了一個(gè)計(jì)數(shù)的作用,真的有點(diǎn)大材小用。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);//為了讓一次輸出10個(gè),初始信號(hào)量為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);//每進(jìn)來1次,信號(hào)量-1;進(jìn)來10次后就一直hold住,直到信號(hào)量大于0;
dispatch_async(queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);//由于這里只是log,所以處理速度非???,我就模擬2秒后信號(hào)量+1;
});
}
dispatch_once
這個(gè)函數(shù)一般是用來做一個(gè)真的單例,也是非常常用的,在這里就舉一個(gè)單例的例子吧:
static SingletonTimer * instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[SingletonTimer alloc] init];
});
return instance;
好了,blog說了這么多關(guān)于GCD中的方法,大家是不是覺得這篇blog并沒有什么高深的理論,本文更傾向于實(shí)用,看完這篇blog之后,大家一定對(duì)GCD躍躍欲試了吧!
參考文獻(xiàn):《Objective-C高級(jí)編程 iOS與OS X多線程》