一.GCD簡介
GCD(Grand Central Dispatch) 是iOS系統一套多線程操作API。字面意思既是宏觀全局派發。相比于其他多線程API,GCD操作簡單,功能強大,且會自動充分利用系統硬件資源。操作簡單是因為我們無需管理線程,只需定義想要執行的任務,然后添加到適當的調度隊列(dispatch queue)。GCD會負責創建線程和調度任務,由系統直接提供線程管理。功能強大是指系統封裝好了很多C函數,調用這些C函數就可以實現線程操作中的絕大多數功能。
GCD里很重要的一個概念是隊列queue,隊列是一種里面的元素按先進先出規則的線性結構。我們可以把任務(往往都是block)往隊列里添加,系統會自動去隊里里取出任務,然后根據當前的隊列是并行隊列 還是串行 隊列,響應的去分配到線程執行。
隊列相當于我們與系統交互的媒介,我們把任務往隊列里扔,系統去隊列里取出任務,開辟線程,分配任務。隊列有并行隊列與串行隊列之分,不同隊列有不同表現形式。
二.并行串行、同步異步介紹
學習GCD,首先要明白兩組概念,并行和串行、同步與異步
并行和串行比較好理解,按字面意思,并行指的并發執行,也就是開辟多個線程同時去執行隊列里任務,串行的表現形式就是添加到隊列里的任務會同時執行,因此后添加進隊列的任務可能會先完成,而先添加進隊列的任務不一定就先完成,串行則是指隊里中的任務嚴格按照添加進隊列的順序先后執行。
同步異步主要是指是否阻塞當前代碼執行, 等待隊列里的任務都執行完才可以繼續執行。同步執行會先執行隊列里的任務,執行完后等待dispatch_sync函數返回才能繼續執行下面操作,異步執行不需要等待隊列任務執行完成,將任務添加到隊列后,dispatch_sync函數馬上返回,繼續執行下面操作,同時另外開辟新線程去執行隊列里的任務。同步執行時候 系統會盡量做到不開辟新線程,只在當前線程里執行隊列中的任務。
三.GCD使用
使用GCD很簡單,兩步即可完成,
1.首先創建隊列,
2.然后將任務放到隊列里。
1.創建隊列
創建隊列 可以使用dispatch_queue_create來創建隊列,需要傳入兩個參數,第一個參數表示隊列的唯一標識符,用于唯一標志隊列,可以為空;第二個參數用來識別是串行隊列還是并行隊列。DISPATCH_QUEUE_SERIAL表示串行隊列,DISPATCH_QUEUE_CONCURRENT表示并行隊列。默認NULL表示串行隊列。
// 串行隊列的創建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并行隊列的創建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT
系統為了方便開發使用,已經定義好了一個全局并發隊列,dispatch_get_global_queue,創建時候有兩個參數,第一個參數表示隊列優先級,一般用默認優先級DISPATCH_QUEUE_PRIORITY_DEFAULT。第二個參數由系統預留做以后使用,現在沒用,可以先用0表示。
2.向隊列里分配任務,任務以block的形式被添加到隊列中
使用同步或者異步兩種方式分配任務
// 同步執行任務創建方法 dispatch_sync(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 所需要執行的任務 });
// 異步執行任務創建方法 dispatch_async(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 所需要執行的任務 });
其中,任務既可以是臨時聲明的block,也可以是提前定義好的block。
這個block是dispatch_async和dispatch_sync的參數
@interface ViewController ()
@property (nonatomic, copy) dispatch_block_t block;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//定義隊列里需要執行的block,即任務
_block = ^(){
for (int i = 0; i < 10 ; i++) {
NSLog(@"%d-------%@",i,[NSThread currentThread]);
}
};
//創建隊列 并行隊列
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
//異步執行創建好的隊列
dispatch_async(queue,_block);
NSLog(@"等不等我");
}
運行結果如下
2017-09-13 00:12:13.995 GCD[1148:97167] 等不等我
2017-09-13 00:12:13.995 GCD[1148:97384] 0-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.997 GCD[1148:97384] 1-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.998 GCD[1148:97384] 2-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.998 GCD[1148:97384] 3-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.999 GCD[1148:97384] 4-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.999 GCD[1148:97384] 5-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.999 GCD[1148:97384] 6-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:14.000 GCD[1148:97384] 7-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:14.000 GCD[1148:97384] 8-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:14.003 GCD[1148:97384] 9-------<NSThread: 0x600000265400>{number = 3, name = (null)}
可以看到使用符合dispatch_block_t 形式的block都可以往隊列里去添加,而dispatch_block_t的類型如下圖所示
可以看到,它就是一個參數與返回值都為空的block。
3.四種情況
兩種隊列和兩種執行方式組合起來有四種效果,分別是同步并行 同步串行 異步并行 和異步串行,
四種方式分別有不同的效果。在這寫條件里,執行方式起主導作用,也就是同步和異步執行起決定因素。
1)同步并行
- (IBAction)syncconcurrence:(id)sender {
NSLog(@"開始");
//創建并行隊列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
//向隊列中添加任務一
dispatch_sync(queue, _block);
//向隊列中添加任務二
dispatch_sync(queue, _block);
//向隊列中添加任務三
dispatch_sync(queue, _block);
NSLog(@"等不等我");
}
運行效果如下
2017-09-13 00:25:32.730 GCD[1181:107620] 開始
2017-09-13 00:25:32.731 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.731 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.731 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.731 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.732 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.732 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.733 GCD[1181:107620] 等不等我
可以看到結果是按添加到隊列里的順序,依次在主線程執行。
同步意味著阻塞,等隊列里的任務執行完 才能繼續往下執行NSLog(@"等不等我")這句代碼,并且不會開辟新線程,盡量在原有線程里執行隊列里的任務,雖然隊列是并發隊列,隊列想要系統去開辟多個線程去執行,但是前面的執行方式已經限定,系統不能開辟新線程,只能在原有線程里面去執行隊列里的任務,結果跟單線程一樣,所以最終結果是依次執行隊列里的任務。
2)同步串行
//同步串行
- (IBAction)syncseria:(id)sender {
NSLog(@"開始");
//創建并行隊列 這里使用NULL缺省表示創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
//向隊列中添加任務一
dispatch_sync(queue, _block);
//向隊列中添加任務二
dispatch_sync(queue, _block);
//向隊列中添加任務三
dispatch_sync(queue, _block);
NSLog(@"等不等我");
}
運行結果如下
2017-09-13 00:29:42.657 GCD[1196:110692] 開始
2017-09-13 00:29:42.657 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.657 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.658 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.659 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.659 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.660 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.660 GCD[1196:110692] 等不等我
同步的意義同上,串行意味著按添加到隊列里的順序去執行隊列里的任務,所以該組合效果與上面的效果是相同的。
3)異步并行
//異步并行
- (IBAction)asyncconcurrence:(id)sender {
NSLog(@"開始");
//創建并行隊列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
//向隊列中添加任務一
dispatch_async(queue, _block);
//向隊列中添加任務二
dispatch_async(queue, _block);
//向隊列中添加任務三
dispatch_async(queue, _block);
NSLog(@"等不等我");
}
2017-09-13 00:53:26.554 GCD[1251:127458] 開始
2017-09-13 00:53:26.554 GCD[1251:127458] 等不等我
2017-09-13 00:53:26.554 GCD[1251:128289] 0-------<NSThread: 0x60800026e6c0>{number = 4, name = (null)}
2017-09-13 00:53:26.554 GCD[1251:128296] 0-------<NSThread: 0x600000079c80>{number = 5, name = (null)}
2017-09-13 00:53:26.554 GCD[1251:128297] 0-------<NSThread: 0x60800026da00>{number = 6, name = (null)}
2017-09-13 00:53:26.557 GCD[1251:128289] 1-------<NSThread: 0x60800026e6c0>{number = 4, name = (null)}
2017-09-13 00:53:26.558 GCD[1251:128296] 1-------<NSThread: 0x600000079c80>{number = 5, name = (null)}
2017-09-13 00:53:26.559 GCD[1251:128297] 1-------<NSThread: 0x60800026da00>{number = 6, name = (null)}
異步并行執行不會阻塞當前代碼執行,系統將任務都添加到隊列后馬上繼續執行下面的代碼,由系統去隊列里取出任務,然后開辟數個線程(線程數量不確定,由系統決定)去執行隊列里的任務。
4)異步串行
//異步串行
- (IBAction)asyncseria:(id)sender {
NSLog(@"開始");
//創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
//向隊列中添加任務一
dispatch_async(queue, _block);
//向隊列中添加任務二
dispatch_async(queue, _block);
//向隊列中添加任務三
dispatch_async(queue, _block);
NSLog(@"等不等我");
}
運行效果如下
2017-09-13 00:53:44.740 GCD[1251:127458] 開始
2017-09-13 00:53:44.740 GCD[1251:127458] 等不等我
2017-09-13 00:53:44.741 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.741 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.742 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.743 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.743 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.743 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
與異步并行的相同點是都不會阻塞當前的代碼執行,系統將任務都添加到隊列后馬上繼續執行下面的代碼,由系統去隊里里取出任務,開辟新線程去執行隊列里的任務。區別是串行執行時候,隊列里的任務只能按順序依次執行,所以系統僅僅開辟一個新線程就可以完成任務。
四種組合,在實際開發中應當如何選取?
同步方式使用較少,因為同步方式會阻塞當前線程,并且按順序執行隊列任務,這樣的效果跟沒有使用隊列的情況下并沒有什么差別,同步執行也不會開辟新線程, 使用意義不是很大。
異步使用場景較多,尤其是涉及到耗時操作,比如網絡請求。在發出網絡請求時候,我們肯定不能讓主線程等待網絡請求結果返回在繼續執行,因此,這里要用異步dispatch_async請求。
串行隊列
//創建并行隊列
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
dispatch_async(queue, ^{
//獲取開始執行任務的相對時間 注意這個單位是納秒
uint64_t timeStrat = mach_absolute_time();
NSLog(@"%llu",timeStrat);
//模擬網絡請求 2秒后完成
sleep(2);
NSLog(@"已完成任務1");
//回到主隊列 也就是主線程里進行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor redColor];
});
});
dispatch_async(queue, ^{
//模擬網絡請求 2秒后完成
sleep(2);
NSLog(@"已完成任務2");
//回到主隊列 也就是主線程里進行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor greenColor];
});
});
dispatch_async(queue, ^{
//模擬網絡請求 2秒后完成
sleep(2);
NSLog(@"已完成任務3");
//回到主隊列 也就是主線程里進行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor purpleColor];
});
//獲取結束任務的相對時間 注意這個單位是納秒
uint64_t timeEnd = mach_absolute_time();
NSLog(@"%llu",timeEnd);
});
執行結果如下:
2017-09-13 15:58:25.534 GCD[3052:231602] 24762306847072
2017-09-13 15:58:27.536 GCD[3052:231602] 已完成任務1
2017-09-13 15:58:29.537 GCD[3052:231602] 已完成任務2
2017-09-13 15:58:31.541 GCD[3052:231602] 已完成任務3
2017-09-13 15:58:31.541 GCD[3052:231602] 24768313894166
可以看到串行隊列會按任務的添加順序依次執行。每個任務會耗時2秒,完成隊列所有任務要花費的時間約為6秒。
并行隊列
僅需要把創建隊列時候的第二個參數設置為DISPATCH_QUEUE_CONCURRENT
即可
//創建并行隊列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//模擬網絡請求 2秒后完成
sleep(2);
NSLog(@"已完成任務1");
//回到主隊列 也就是主線程里進行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor redColor];
});
});
dispatch_async(queue, ^{
//模擬網絡請求 2秒后完成
sleep(2);
NSLog(@"已完成任務2");
//回到主隊列 也就是主線程里進行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor greenColor];
});
});
dispatch_async(queue, ^{
//模擬網絡請求 2秒后完成
sleep(2);
NSLog(@"已完成任務3");
//回到主隊列 也就是主線程里進行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor purpleColor];
});
});
執行效果如下
2017-09-13 15:51:43.429 GCD[3022:227333] 已完成任務2
2017-09-13 15:51:43.429 GCD[3022:227326] 已完成任務1
2017-09-13 15:51:43.429 GCD[3022:227332] 已完成任務3
可以看到并行不一定會按任務添加順序執行,完全由系統取出任務,分配到不同線程去執行,所以先后順序也會不同。