在iOS中,實現多線程的方式有很多,比如GCD,NSOperation,NSThread等等,但是一直對線程的概念模糊,今天就根據代碼例子來了解iOS中GCD的用法和原理。
GCD是和block緊密相連的,所以最好先了解下block。
GCD是C level的函數,這意味著它也提供了C的函數指針作為參數,方便了C程序員。
下面首先來看GCD的使用:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async表明異步運行,block代表的是你要做的事情,queue則是你把任務交給誰來處理了。(除了async,還有sync,delay,本文以async為例)。
dispatch隊列不支持cancel(取消),沒有實現dispatch_cancel()函數,不像NSOperationQueue,不得不說這是個小小的缺憾。
之所以程序中會用到多線程是因為程序往往會需要讀取數據,然后更新UI。為了良好的用戶體驗,讀取數據的操作會傾向于在后臺運行,這樣以避免阻塞主線程。GCD里就有三種queue來處理。
GCD Queue分為三種:
- The main queue:主隊列,主線程就是在個隊列中,系統默認就有一個串行隊列
- Global queues: 全局并發隊列
- 用戶隊列:是用函數dispatch_queue_create創建的自定義隊列
而用戶隊列又分為下面兩種:
(1)Serial Dispatch Queue
線程池只提供一個線程用來執行任務,所以后一個任務必須等到前一個任務執行結束才能開始。(DISPATCH_QUEUE_SERIAL)
(2)Concurrent Dispatch Queue
線程池提供多個線程來執行任務,所以可以按序啟動多個任務并發執行。(DISPATCH_QUEUE_CONCURRENT)
關于同步和異步
dispatch_sync
則調用用 dispatch_sync的線程會等dispatch_sync的對內容執行完再繼續執行。dispatch_sync函數不會立即返回,即阻塞當前線程,等待block同步執行完成。dispatch_async
調用dispatch_async的線程不會的等dispatch_async的內容,自己繼續執行。dispatch_async函數會立即返回, block會在后臺異步執行。
關于并發和并行
并行:是多個任務在同一個時間片段內同時進行,比如說你一邊吃飯一邊打電話一遍看電視
并發:是多個任務在同一個時間的點上可以切換進行,比如說你先吃著飯,然后電話來了,停下吃飯立馬打電話,打完后立馬切換到看電視,這個你在同一個線條內只有一個任務在執行
圖示關于并發和并行
下面就以代碼實例來分析GCD用法
實例一:DISPATCH_QUEUE_SERIAL串行隊列
/**
DISPATCH_QUEUE_SERIAL是每次運行一個任務,可以添加多個,執行次序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中異步將任務放到myQueue隊列里
//放入myQueue的順序是按代碼的執行順序:任務一,任務二,任務三(因為main_queue是順序執行)
//在myQueue取出的順序是按照FIFO的順序
//因為這是一個串行的隊列,所以取出后的任務是一個接著一個執行的
//執行結果
//[NSThread sleepForTimeInterval:6]~~~~~~~~6
//[NSThread sleepForTimeInterval:3]~~~~~~~~9
//[NSThread sleepForTimeInterval:12]~~~~~~~~21
//任務一:
dispatch_async(myQueue, ^{
[NSThread sleepForTimeInterval:6];
NSLog(@"[NSThread sleepForTimeInterval:6]~~~~~~~~%d", t);
});
//任務二:
dispatch_async(myQueue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"[NSThread sleepForTimeInterval:3]~~~~~~~~%d", t);
});
//任務三:
dispatch_async(myQueue, ^{
[NSThread sleepForTimeInterval:12];
NSLog(@"[NSThread sleepForTimeInterval:12]~~~~~~~~%d", t);
});
}
-(void)addTime {
++t;
}
實例二:全局的并發隊列dispatch_get_global_queue
/**
可以同時運行多個任務,每個任務的啟動時間是按照加入queue的順序,結束的順序依賴各自的任務.
**/
- (void)test2
{
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//在main_queue中異步將任務放到myQueue
//放入myQueue的順序是:任務一,任務二,任務三
//在myQueue中取出的順序是FIFO的原則:任務一,任務二,任務三
//取出的任務放到線程里執行,因為這是一個并行的隊列,所以任務可以同時運行
//執行的結果
//執行的結果可以是 1 2 3 的隨機組合
//任務一:
dispatch_async(myQueue, ^{
NSLog(@"~~~~~~~~~1");
});
//任務二:
dispatch_async(myQueue, ^{
NSLog(@"~~~~~~~~~2");
});
//任務三:
dispatch_async(myQueue, ^{
NSLog(@"~~~~~~~~~3");
});
}
實例三:DISPATCH_QUEUE_SERIAL串行隊列
/**
串行隊列的異步任務:使用一個子線程依次執行。
對比一下dispatch_async和dispatch_sync輸出的i的順序和線程的地址
**/
- (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的任務異步加入queue隊列中
//加入queue的順序是:i=0, i=1, i=2
//所以在queue中取出的順序是:i=0,i=1,i=2
//因為queue是同步隊列,所以三個任務放到同一個線程中依次執行
//運行結果
//~~~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)}
//當是使用DISPATCH_QUEUE_SERIAL和dispatch_sync同步的時候,三個任務執行的線程就是當前的mainThread中執行的
dispatch_async(queue, ^{
NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
NSLog(@"%@",[NSThread mainThread]);
});
}
}
實例四:DISPATCH_QUEUE_CONCURRENT并發隊列
/**
并行隊列的異步任務:使用多個子線程無序執行,一般任務較少時幾個任務就開幾個線程,較多時則開部分線程。
應用:一系列的異步任務沒有先后順序,結束無序
對比一下dispatch_async和dispatch_sync輸出的線程的地址
**/
- (void)test4
{
dispatch_queue_t queue = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
//在main_queue中異步的將i=0,i=1,i=2的任務加入到queue中
//加入queue的順序是i=0,i=1,i=2
//在queue中取出的順序是i=0,i=1,i=2
//因為queue是并發隊列,所以有多條的線程來執行三個任務,thread的執行的順序不定
//當使用dispatch_sync時,執行的順序又成了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]);
});
}
}
實例五:dispatch_group_t組隊列
/**
dispatch_group_async可以實現監聽一組任務是否完成,完成后得到通知執行其他的操作。
dispatch_group_async會監聽最終的任務完成后,并通知一個線程
這個方法很有用,比如你執行三個下載任務,當三個任務都下載完成后你才通知界面說完成了。
注意這里不是監聽queue里所有的任務完成,而是添加到組里的任務,這個任務是在這個queue里,同時也在這個組里,組里所有的任務的完成并不代表queue里所有的任務的完成。
下面是一段例子代碼:
注意:當queue是global(或者DISPATCH_QUEUE_CONCURRENT)隊列和DISPATCH_QUEUE_SERIAL隊列時線程的區別
**/
- (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(@"回到主線程");
});
}
實例六:dispatch_apply重復
/**
dispatch_apply:執行某個代碼片段N次
重復執行block,需要注意的是這個方法是同步返回,也就是說等到所有block執行完畢才返回,如需異步返回則嵌套在dispatch_async中來使用。
多個block的運行是否并發或串行執行也依賴queue的是否并發或串行。
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
運行結果:
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);
運行結果:
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里所有的任務執行完畢之后才會執行
dispatch_async(queue2, ^(){
//放在dispatch_apply里面的任務的執行順序完全依賴于queue1的隊列串發還是并發
dispatch_apply([array count], queue1, ^(size_t index) {
NSLog(@"array[%ld]~~~~~~~~~~~~~~%@", index, array[index]);
});
NSLog(@"done");
});
}
實例七:dispatch_barrier_async
/**
dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任務執行結束后它才執行,而且它后面的任務等它執行完成之后才會執行
只有當這個隊列為自己創建的并發隊列(DISPATCH_QUEUE_CONCURRENT)時才會有這種效果
執行結果(一)為:
dispatch_async2
dispatch_async1
dispatch_barrier_async
dispatch_async3
如果使用dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
根據官方文檔指出,這個時候的dispatch_barrier_async完全等同于dispatch_async
執行結果(二)為:
dispatch_barrier_async
dispatch_async3
dispatch_async2
dispatch_async1
在使用DISPATCH_QUEUE_SERIAL串行隊列時,完全就按照FIFO的順序執行了
執行結果(三)為:
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方法,兩個方法的區別是:
dispatch_barrier_async當他把這個barrier添加到隊列后,當前隊列不用等待block的執行返回
而dispatch_barrier_sync需要等待block的內容執行完畢之后再繼續下面的執行
**/
- (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]);
});
}
實例八:dispatch_once一次使用函數
/**
dispatch_once
dispatch_once這個函數,它可以保證整個應用程序生命周期中某段代碼只被執行一次!
**/
- (void)test8
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
}
實例九:dispatch_after延遲函數
/**
dispatch_after
有時候我們需要等個幾秒鐘然后做個動畫或者給個提示,這時候可以用dispatch_after這個函數:
**/
-(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
});
}
實例十:dispatch_set_target_queue轉換隊列
/**
dispatch_set_target_queue
通過dispatch_set_target_queue函數可以設置一個dispatch queue的優先級,或者指定一個dispatch source相應的事件處理提交到哪個queue上。
它會把需要執行的任務對象指定到不同的隊列中去處理,這個任務對象可以是dispatch隊列,也可以是dispatch源。而且這個過程可以是動態的,可以實現隊列的動態調度管理等等。比如說有兩個隊列dispatchA和dispatchB,這時把dispatchA指派到dispatchB:
dispatch_set_target_queue(dispatchA, dispatchB);
那么dispatchA上還未運行的block會放到dispatchB上,然后由dispatchB來進行管理運行。
**/
dispatch_set_target_queue(serialQ, globalQ);
實例十一:dispatch_group_wait等待
//dispatch_group_wait來等待這些任務完成。若任務已經全部完成或為空,則直接返回,否則等待所有任務完成后返回。
//dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
-(void)test10
{
dispatch_group_t group = dispatch_group_create();
//注意queue為DISPATCH_QUEUE_CONCURRENT 和 DISPATCH_QUEUE_SERIAL時當前的線程
dispatch_queue_t queue = dispatch_queue_create([@"com.queue" UTF8String], DISPATCH_QUEUE_SERIAL);
//每個dispatch_group_enter對應一個dispatch_group_leave完成group內所有的任務則發送通知
//進入group
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task1~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
//離開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");
});
}
補充:dispatch_sync(dispatch_get_main_queue(),...)造成死鎖的原因
當這段代碼放在主線程里,也即dispatch_get_main_queue()中,執行到sync時向dispatch_get_main_queue()插入同步thread,sync會等到里面的block執行完成才返回。sync又在主隊列里面,是個串行隊列,sync是后面才加入的,前面一個是主線程,所以sync想執行block必須等待前一個主線程執行完成,而主線程卻在等待sync返回,才能執行后續工作,從而造成死鎖。