在iOS中,實(shí)現(xiàn)多線(xiàn)程的方式有很多,比如GCD,NSOperation,NSThread等等,但是一直對(duì)線(xiàn)程的概念模糊,今天就根據(jù)代碼例子來(lái)了解iOS中GCD的用法和原理。
GCD是和block緊密相連的,所以最好先了解下block。
GCD是C level的函數(shù),這意味著它也提供了C的函數(shù)指針作為參數(shù),方便了C程序員。
下面首先來(lái)看GCD的使用:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async表明異步運(yùn)行,block代表的是你要做的事情,queue則是你把任務(wù)交給誰(shuí)來(lái)處理了。(除了async,還有sync,delay,本文以async為例)。
dispatch隊(duì)列不支持cancel(取消),沒(méi)有實(shí)現(xiàn)dispatch_cancel()函數(shù),不像NSOperationQueue,不得不說(shuō)這是個(gè)小小的缺憾。
之所以程序中會(huì)用到多線(xiàn)程是因?yàn)槌绦蛲鶗?huì)需要讀取數(shù)據(jù),然后更新UI。為了良好的用戶(hù)體驗(yàn),讀取數(shù)據(jù)的操作會(huì)傾向于在后臺(tái)運(yùn)行,這樣以避免阻塞主線(xiàn)程。GCD里就有三種queue來(lái)處理。
GCD Queue分為三種:
- The main queue:主隊(duì)列,主線(xiàn)程就是在個(gè)隊(duì)列中,系統(tǒng)默認(rèn)就有一個(gè)串行隊(duì)列
- Global queues: 全局并發(fā)隊(duì)列
- 用戶(hù)隊(duì)列:是用函數(shù)dispatch_queue_create創(chuàng)建的自定義隊(duì)列
而用戶(hù)隊(duì)列又分為下面兩種:
(1)Serial Dispatch Queue
線(xiàn)程池只提供一個(gè)線(xiàn)程用來(lái)執(zhí)行任務(wù),所以后一個(gè)任務(wù)必須等到前一個(gè)任務(wù)執(zhí)行結(jié)束才能開(kāi)始。(DISPATCH_QUEUE_SERIAL)
(2)Concurrent Dispatch Queue
線(xiàn)程池提供多個(gè)線(xiàn)程來(lái)執(zhí)行任務(wù),所以可以按序啟動(dòng)多個(gè)任務(wù)并發(fā)執(zhí)行。(DISPATCH_QUEUE_CONCURRENT)
關(guān)于同步和異步
dispatch_sync
則調(diào)用用 dispatch_sync的線(xiàn)程會(huì)等dispatch_sync的對(duì)內(nèi)容執(zhí)行完再繼續(xù)執(zhí)行。dispatch_sync函數(shù)不會(huì)立即返回,即阻塞當(dāng)前線(xiàn)程,等待block同步執(zhí)行完成。dispatch_async
調(diào)用dispatch_async的線(xiàn)程不會(huì)的等dispatch_async的內(nèi)容,自己繼續(xù)執(zhí)行。dispatch_async函數(shù)會(huì)立即返回, block會(huì)在后臺(tái)異步執(zhí)行。
關(guān)于并發(fā)和并行
并行:是多個(gè)任務(wù)在同一個(gè)時(shí)間片段內(nèi)同時(shí)進(jìn)行,比如說(shuō)你一邊吃飯一邊打電話(huà)一遍看電視
并發(fā):是多個(gè)任務(wù)在同一個(gè)時(shí)間的點(diǎn)上可以切換進(jìn)行,比如說(shuō)你先吃著飯,然后電話(huà)來(lái)了,停下吃飯立馬打電話(huà),打完后立馬切換到看電視,這個(gè)你在同一個(gè)線(xiàn)條內(nèi)只有一個(gè)任務(wù)在執(zhí)行
圖示關(guān)于并發(fā)和并行
下面就以代碼實(shí)例來(lái)分析GCD用法
實(shí)例一:DISPATCH_QUEUE_SERIAL串行隊(duì)列
/**
DISPATCH_QUEUE_SERIAL是每次運(yùn)行一個(gè)任務(wù),可以添加多個(gè),執(zhí)行次序FIFO。
**/
- (void)test1
{
NSDate *date = [NSDate date];
NSString *daStr = [date description];
const char *queueName = [daStr UTF8String];
//DISPATCH_QUEUE_SERIAL等同于NULL
dispatch_queue_t myQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(addTime) userInfo:nil repeats:YES];
//在main_queue中異步將任務(wù)放到myQueue隊(duì)列里
//放入myQueue的順序是按代碼的執(zhí)行順序:任務(wù)一,任務(wù)二,任務(wù)三(因?yàn)閙ain_queue是順序執(zhí)行)
//在myQueue取出的順序是按照FIFO的順序
//因?yàn)檫@是一個(gè)串行的隊(duì)列,所以取出后的任務(wù)是一個(gè)接著一個(gè)執(zhí)行的
//執(zhí)行結(jié)果
//[NSThread sleepForTimeInterval:6]~~~~~~~~6
//[NSThread sleepForTimeInterval:3]~~~~~~~~9
//[NSThread sleepForTimeInterval:12]~~~~~~~~21
//任務(wù)一:
dispatch_async(myQueue, ^{
[NSThread sleepForTimeInterval:6];
NSLog(@"[NSThread sleepForTimeInterval:6]~~~~~~~~%d", t);
});
//任務(wù)二:
dispatch_async(myQueue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"[NSThread sleepForTimeInterval:3]~~~~~~~~%d", t);
});
//任務(wù)三:
dispatch_async(myQueue, ^{
[NSThread sleepForTimeInterval:12];
NSLog(@"[NSThread sleepForTimeInterval:12]~~~~~~~~%d", t);
});
}
-(void)addTime {
++t;
}
實(shí)例二:全局的并發(fā)隊(duì)列dispatch_get_global_queue
/**
可以同時(shí)運(yùn)行多個(gè)任務(wù),每個(gè)任務(wù)的啟動(dòng)時(shí)間是按照加入queue的順序,結(jié)束的順序依賴(lài)各自的任務(wù).
**/
- (void)test2
{
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//在main_queue中異步將任務(wù)放到myQueue
//放入myQueue的順序是:任務(wù)一,任務(wù)二,任務(wù)三
//在myQueue中取出的順序是FIFO的原則:任務(wù)一,任務(wù)二,任務(wù)三
//取出的任務(wù)放到線(xiàn)程里執(zhí)行,因?yàn)檫@是一個(gè)并行的隊(duì)列,所以任務(wù)可以同時(shí)運(yùn)行
//執(zhí)行的結(jié)果
//執(zhí)行的結(jié)果可以是 1 2 3 的隨機(jī)組合
//任務(wù)一:
dispatch_async(myQueue, ^{
NSLog(@"~~~~~~~~~1");
});
//任務(wù)二:
dispatch_async(myQueue, ^{
NSLog(@"~~~~~~~~~2");
});
//任務(wù)三:
dispatch_async(myQueue, ^{
NSLog(@"~~~~~~~~~3");
});
}
實(shí)例三:DISPATCH_QUEUE_SERIAL串行隊(duì)列
/**
串行隊(duì)列的異步任務(wù):使用一個(gè)子線(xiàn)程依次執(zhí)行。
對(duì)比一下dispatch_async和dispatch_sync輸出的i的順序和線(xiàn)程的地址
**/
- (void)test3
{
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 3; i++) {
//在main_queue中將i=0,1,2的任務(wù)異步加入queue隊(duì)列中
//加入queue的順序是:i=0, i=1, i=2
//所以在queue中取出的順序是:i=0,i=1,i=2
//因?yàn)閝ueue是同步隊(duì)列,所以三個(gè)任務(wù)放到同一個(gè)線(xiàn)程中依次執(zhí)行
//運(yùn)行結(jié)果
//~~~i=0~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
//<NSThread: 0x600000068d00>{number = 1, name = (null)}
//~~~i=1~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
//<NSThread: 0x600000068d00>{number = 1, name = (null)}
// ~~~i=2~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
//<NSThread: 0x600000068d00>{number = 1, name = (null)}
//當(dāng)是使用DISPATCH_QUEUE_SERIAL和dispatch_sync同步的時(shí)候,三個(gè)任務(wù)執(zhí)行的線(xiàn)程就是當(dāng)前的mainThread中執(zhí)行的
dispatch_async(queue, ^{
NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
NSLog(@"%@",[NSThread mainThread]);
});
}
}
實(shí)例四:DISPATCH_QUEUE_CONCURRENT并發(fā)隊(duì)列
/**
并行隊(duì)列的異步任務(wù):使用多個(gè)子線(xiàn)程無(wú)序執(zhí)行,一般任務(wù)較少時(shí)幾個(gè)任務(wù)就開(kāi)幾個(gè)線(xiàn)程,較多時(shí)則開(kāi)部分線(xiàn)程。
應(yīng)用:一系列的異步任務(wù)沒(méi)有先后順序,結(jié)束無(wú)序
對(duì)比一下dispatch_async和dispatch_sync輸出的線(xiàn)程的地址
**/
- (void)test4
{
dispatch_queue_t queue = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
//在main_queue中異步的將i=0,i=1,i=2的任務(wù)加入到queue中
//加入queue的順序是i=0,i=1,i=2
//在queue中取出的順序是i=0,i=1,i=2
//因?yàn)閝ueue是并發(fā)隊(duì)列,所以有多條的線(xiàn)程來(lái)執(zhí)行三個(gè)任務(wù),thread的執(zhí)行的順序不定
//當(dāng)使用dispatch_sync時(shí),執(zhí)行的順序又成了i=0,i=1,i=2
for (int i = 0; i < 3; i++) {
dispatch_async(queue, ^{
NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
NSLog(@"%@",[NSThread mainThread]);
});
}
}
實(shí)例五:dispatch_group_t組隊(duì)列
/**
dispatch_group_async可以實(shí)現(xiàn)監(jiān)聽(tīng)一組任務(wù)是否完成,完成后得到通知執(zhí)行其他的操作。
dispatch_group_async會(huì)監(jiān)聽(tīng)最終的任務(wù)完成后,并通知一個(gè)線(xiàn)程
這個(gè)方法很有用,比如你執(zhí)行三個(gè)下載任務(wù),當(dāng)三個(gè)任務(wù)都下載完成后你才通知界面說(shuō)完成了。
注意這里不是監(jiān)聽(tīng)queue里所有的任務(wù)完成,而是添加到組里的任務(wù),這個(gè)任務(wù)是在這個(gè)queue里,同時(shí)也在這個(gè)組里,組里所有的任務(wù)的完成并不代表queue里所有的任務(wù)的完成。
下面是一段例子代碼:
注意:當(dāng)queue是global(或者DISPATCH_QUEUE_CONCURRENT)隊(duì)列和DISPATCH_QUEUE_SERIAL隊(duì)列時(shí)線(xiàn)程的區(qū)別
**/
- (void)test5
{
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue = dispatch_queue_create("queue3", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"task1~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"task2~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"task3~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"回到主線(xiàn)程");
});
}
實(shí)例六:dispatch_apply重復(fù)
/**
dispatch_apply:執(zhí)行某個(gè)代碼片段N次
重復(fù)執(zhí)行block,需要注意的是這個(gè)方法是同步返回,也就是說(shuō)等到所有block執(zhí)行完畢才返回,如需異步返回則嵌套在dispatch_async中來(lái)使用。
多個(gè)block的運(yùn)行是否并發(fā)或串行執(zhí)行也依賴(lài)queue的是否并發(fā)或串行。
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
運(yùn)行結(jié)果:
array[0]~~~~~~~~~~~~~~0
array[3]~~~~~~~~~~~~~~3
array[2]~~~~~~~~~~~~~~2
array[1]~~~~~~~~~~~~~~1
array[4]~~~~~~~~~~~~~~4
array[5]~~~~~~~~~~~~~~5
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
運(yùn)行結(jié)果:
array[0]~~~~~~~~~~~~~~0
array[1]~~~~~~~~~~~~~~1
array[2]~~~~~~~~~~~~~~2
array[3]~~~~~~~~~~~~~~3
array[4]~~~~~~~~~~~~~~4
array[5]~~~~~~~~~~~~~~5
**/
- (void)test6
{
NSArray *array = @[@"0",@"1",@"2",@"3",@"4",@"5"];
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
//打印done是在dispatch_apply里所有的任務(wù)執(zhí)行完畢之后才會(huì)執(zhí)行
dispatch_async(queue2, ^(){
//放在dispatch_apply里面的任務(wù)的執(zhí)行順序完全依賴(lài)于queue1的隊(duì)列串發(fā)還是并發(fā)
dispatch_apply([array count], queue1, ^(size_t index) {
NSLog(@"array[%ld]~~~~~~~~~~~~~~%@", index, array[index]);
});
NSLog(@"done");
});
}
實(shí)例七:dispatch_barrier_async
/**
dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會(huì)執(zhí)行
只有當(dāng)這個(gè)隊(duì)列為自己創(chuàng)建的并發(fā)隊(duì)列(DISPATCH_QUEUE_CONCURRENT)時(shí)才會(huì)有這種效果
執(zhí)行結(jié)果(一)為:
dispatch_async2
dispatch_async1
dispatch_barrier_async
dispatch_async3
如果使用dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
根據(jù)官方文檔指出,這個(gè)時(shí)候的dispatch_barrier_async完全等同于dispatch_async
執(zhí)行結(jié)果(二)為:
dispatch_barrier_async
dispatch_async3
dispatch_async2
dispatch_async1
在使用DISPATCH_QUEUE_SERIAL串行隊(duì)列時(shí),完全就按照FIFO的順序執(zhí)行了
執(zhí)行結(jié)果(三)為:
2015-12-23 16:53:04.574 NSURLDemo[67716:3661098] dispatch_async1
2015-12-23 16:53:05.577 NSURLDemo[67716:3661098] dispatch_async2
2015-12-23 16:53:05.578 NSURLDemo[67716:3661098] dispatch_barrier_async
2015-12-23 16:53:07.085 NSURLDemo[67716:3661098] dispatch_async3
不僅有dispatch_barrier_async方法,還有dispatch_barrier_sync方法,兩個(gè)方法的區(qū)別是:
dispatch_barrier_async當(dāng)他把這個(gè)barrier添加到隊(duì)列后,當(dāng)前隊(duì)列不用等待block的執(zhí)行返回
而dispatch_barrier_sync需要等待block的內(nèi)容執(zhí)行完畢之后再繼續(xù)下面的執(zhí)行
**/
- (void)test7
{
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"dispatch_async1~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async2~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
});
dispatch_barrier_async(queue, ^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"dispatch_barrier_async~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
});
}
實(shí)例八:dispatch_once一次使用函數(shù)
/**
dispatch_once
dispatch_once這個(gè)函數(shù),它可以保證整個(gè)應(yīng)用程序生命周期中某段代碼只被執(zhí)行一次!
**/
- (void)test8
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
}
實(shí)例九:dispatch_after延遲函數(shù)
/**
dispatch_after
有時(shí)候我們需要等個(gè)幾秒鐘然后做個(gè)動(dòng)畫(huà)或者給個(gè)提示,這時(shí)候可以用dispatch_after這個(gè)函數(shù):
**/
-(void)test9
{
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queue after delay
});
}
實(shí)例十:dispatch_set_target_queue轉(zhuǎn)換隊(duì)列
/**
dispatch_set_target_queue
通過(guò)dispatch_set_target_queue函數(shù)可以設(shè)置一個(gè)dispatch queue的優(yōu)先級(jí),或者指定一個(gè)dispatch source相應(yīng)的事件處理提交到哪個(gè)queue上。
它會(huì)把需要執(zhí)行的任務(wù)對(duì)象指定到不同的隊(duì)列中去處理,這個(gè)任務(wù)對(duì)象可以是dispatch隊(duì)列,也可以是dispatch源。而且這個(gè)過(guò)程可以是動(dòng)態(tài)的,可以實(shí)現(xiàn)隊(duì)列的動(dòng)態(tài)調(diào)度管理等等。比如說(shuō)有兩個(gè)隊(duì)列dispatchA和dispatchB,這時(shí)把dispatchA指派到dispatchB:
dispatch_set_target_queue(dispatchA, dispatchB);
那么dispatchA上還未運(yùn)行的block會(huì)放到dispatchB上,然后由dispatchB來(lái)進(jìn)行管理運(yùn)行。
**/
dispatch_set_target_queue(serialQ, globalQ);
實(shí)例十一:dispatch_group_wait等待
//dispatch_group_wait來(lái)等待這些任務(wù)完成。若任務(wù)已經(jīng)全部完成或?yàn)榭眨瑒t直接返回,否則等待所有任務(wù)完成后返回。
//dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
-(void)test10
{
dispatch_group_t group = dispatch_group_create();
//注意queue為DISPATCH_QUEUE_CONCURRENT 和 DISPATCH_QUEUE_SERIAL時(shí)當(dāng)前的線(xiàn)程
dispatch_queue_t queue = dispatch_queue_create([@"com.queue" UTF8String], DISPATCH_QUEUE_SERIAL);
//每個(gè)dispatch_group_enter對(duì)應(yīng)一個(gè)dispatch_group_leave完成group內(nèi)所有的任務(wù)則發(fā)送通知
//進(jìn)入group
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task1~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
//離開(kāi)group
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task2~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
dispatch_group_leave(group);
});
NSLog(@"~~~~~~~~~~~~~~~~~~before group wait");
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"~~~~~~~~~~~~~~~~~~after group wait");
dispatch_group_notify(group, queue, ^{
NSLog(@"~~~~~~~~~~~~~allGroupFinish");
});
}
補(bǔ)充:dispatch_sync(dispatch_get_main_queue(),...)造成死鎖的原因
當(dāng)這段代碼放在主線(xiàn)程里,也即dispatch_get_main_queue()中,執(zhí)行到sync時(shí)向dispatch_get_main_queue()插入同步thread,sync會(huì)等到里面的block執(zhí)行完成才返回。sync又在主隊(duì)列里面,是個(gè)串行隊(duì)列,sync是后面才加入的,前面一個(gè)是主線(xiàn)程,所以sync想執(zhí)行block必須等待前一個(gè)主線(xiàn)程執(zhí)行完成,而主線(xiàn)程卻在等待sync返回,才能執(zhí)行后續(xù)工作,從而造成死鎖。