GCD
簡(jiǎn)介
- GCD全稱是
Grand Central Dispatch -
純C語言,提供例如非常強(qiáng)大的函數(shù)
優(yōu)勢(shì)
- GCD是蘋果公司為
多核的并行運(yùn)算提出的解決方案 - GCD會(huì)
自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核) - GCD會(huì)
自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程) - 程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼
【重點(diǎn)】用一句話總結(jié)GCD就是:將任務(wù)添加到隊(duì)列,并指定任務(wù)執(zhí)行的函數(shù)
核心
在日常開發(fā)中,GCD一般寫成下面這種形式
dispatch_async( dispatch_queue_create("com.sunrise.queue", NULL), ^{
NSLog(@"GCD基本使用");
});
將上述代碼拆分,方便我們來理解GCD的核心 主要是由 任務(wù) + 隊(duì)列 + 函數(shù) 構(gòu)成
//********GCD基礎(chǔ)寫法********
//創(chuàng)建任務(wù)
dispatch_block_t block = ^{
NSLog(@"hello GCD");
};
//創(chuàng)建串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("com.sunrise.queue", NULL);
//將任務(wù)添加到隊(duì)列,并指定函數(shù)執(zhí)行
dispatch_async(queue, block);
- 使用
dispatch_block_t創(chuàng)建任務(wù) - 使用
dispatch_queue_t創(chuàng)建隊(duì)列 - 將
任務(wù)添加到隊(duì)列,并指定執(zhí)行任務(wù)的函數(shù)dispatch_async
注意
這里的任務(wù)是指執(zhí)行操作的意思,在使用dispatch_block_t創(chuàng)建任務(wù)時(shí),主要有以下兩點(diǎn)說明
- 任務(wù)使用
block封裝 - 任務(wù)的block
沒有參數(shù)也沒有返回值
函數(shù)與隊(duì)列
函數(shù)
在GCD中執(zhí)行任務(wù)的方式有兩種,同步執(zhí)行和異步執(zhí)行,分別對(duì)應(yīng) 同步函數(shù)dispatch_sync 和 異步函數(shù)dispatch_async,兩者對(duì)比如下
-
同步執(zhí)行,對(duì)應(yīng)同步函數(shù)
dispatch_sync- 必須等待當(dāng)前語句
執(zhí)行完畢,才會(huì)執(zhí)行下一條語句 -
不會(huì)開啟線程,即不具備開啟新線程的能力 - 在當(dāng)前線程中執(zhí)行block任務(wù)
- 必須等待當(dāng)前語句
-
異步執(zhí)行,對(duì)應(yīng)異步函數(shù)
dispatch_async- 不用等待當(dāng)前語句執(zhí)行完畢,就可以執(zhí)行下一條語句
- 會(huì)
開啟線程執(zhí)行block任務(wù),即具備開啟新線程的能力(但并不一定開啟新線程,這個(gè)與任務(wù)所指定的隊(duì)列類型有關(guān)) -
異步是多線程的代名詞
所以,綜上所述,兩種執(zhí)行方式的主要區(qū)別有兩點(diǎn):
-
是否等待隊(duì)列的任務(wù)執(zhí)行完畢 -
是否具備開啟新線程的能力
隊(duì)列
串行隊(duì)列 和 并發(fā)隊(duì)列
多線程中所說的隊(duì)列(Dispatch Queue)是指執(zhí)行任務(wù)的等待隊(duì)列,即用來存放任務(wù)的隊(duì)列。隊(duì)列是一種特殊的線性表,遵循先進(jìn)先出(FIFO)原則,即新任務(wù)總是被插入到隊(duì)尾,而任務(wù)的讀取從隊(duì)首開始讀取。每讀取一個(gè)任務(wù),則動(dòng)隊(duì)列中釋放一個(gè)任務(wù),如下圖所示

在GCD中,隊(duì)列主要分為串行隊(duì)列(Serial Dispatch Queue) 和并發(fā)隊(duì)列(Concurrent Dispatch Queue)兩種,如下圖所示

-
串行隊(duì)列:每次只有一個(gè)任務(wù)被執(zhí)行,等待上一個(gè)任務(wù)執(zhí)行完畢再執(zhí)行下一個(gè),即只開啟一個(gè)線程(通俗理解:同一時(shí)刻只調(diào)度一個(gè)任務(wù)執(zhí)行)- 使用
dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);創(chuàng)建串行隊(duì)列 - 其中的
DISPATCH_QUEUE_SERIAL也可以使用NULL表示,這兩種均表示默認(rèn)的串行隊(duì)列
- 使用
// 串行隊(duì)列的獲取方法
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.sunrise.queue", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.sunrise.queue", DISPATCH_QUEUE_SERIAL);
-
并發(fā)隊(duì)列:一次可以并發(fā)執(zhí)行多個(gè)任務(wù),即開啟多個(gè)線程,并同時(shí)執(zhí)行任務(wù)(通俗理解:同一時(shí)刻可以調(diào)度多個(gè)任務(wù)執(zhí)行)- 使用
dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);創(chuàng)建并發(fā)隊(duì)列 - 注意:并發(fā)隊(duì)列的并發(fā)功能只有在
異步函數(shù)下才有效
- 使用
// 并發(fā)隊(duì)列的獲取方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.sunrise.queue", DISPATCH_QUEUE_CONCURRENT);
主隊(duì)列 和 全局并發(fā)隊(duì)列
在GCD中,針對(duì)這兩種隊(duì)列,分別提供了主隊(duì)列(Main Dispatch Queue)和全局并發(fā)隊(duì)列(Global Dispatch Queue)
-
主隊(duì)列(Main Dispatch Queue):GCD中提供的特殊的串行隊(duì)列- 專門用來
在主線程上調(diào)度任務(wù)的串行隊(duì)列,依賴于主線程、主Runloop,在main函數(shù)調(diào)用之前自動(dòng)創(chuàng)建 - 不會(huì)開啟線程
- 如果當(dāng)前主線程正在有任務(wù)執(zhí)行,那么無論主隊(duì)列中當(dāng)前被添加了什么任務(wù),都不會(huì)被調(diào)度
- 使用
dispatch_get_main_queue()獲得主隊(duì)列 - 通常在返回
主線程更新UI時(shí)使用
- 專門用來
//主隊(duì)列的獲取方法
dispatch_queue_t mainQueue = dispatch_get_main_queue();
-
全局并發(fā)隊(duì)列(Global Dispatch Queue):GCD提供的默認(rèn)的并發(fā)隊(duì)列- 為了方便程序員的使用,蘋果提供了全局隊(duì)列
- 在使用多線程開發(fā)時(shí),如果對(duì)隊(duì)列沒有特殊需求,
在執(zhí)行異步任務(wù)時(shí),可以直接使用全局隊(duì)列 - 使用
dispatch_get_global_queue獲取全局并發(fā)隊(duì)列,最簡(jiǎn)單的是dispatch_get_global_queue(0, 0)- 第一個(gè)參數(shù)表示隊(duì)列優(yōu)先級(jí),默認(rèn)優(yōu)先級(jí)為
DISPATCH_QUEUE_PRIORITY_DEFAULT=0,在iOS9之后,已經(jīng)被服務(wù)質(zhì)量(quality-of-service)取代 - 第二個(gè)參數(shù)使用0
- 第一個(gè)參數(shù)表示隊(duì)列優(yōu)先級(jí),默認(rèn)優(yōu)先級(jí)為
//全局并發(fā)隊(duì)列的獲取方法
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
//優(yōu)先級(jí)從高到低(對(duì)應(yīng)的服務(wù)質(zhì)量)依次為
- DISPATCH_QUEUE_PRIORITY_HIGH -- QOS_CLASS_USER_INITIATED
- DISPATCH_QUEUE_PRIORITY_DEFAULT -- QOS_CLASS_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW -- QOS_CLASS_UTILITY
- DISPATCH_QUEUE_PRIORITY_BACKGROUND -- QOS_CLASS_BACKGROUND
全局并發(fā)隊(duì)列 + 主隊(duì)列 配合使用
在日常開發(fā)中,全局隊(duì)列+并發(fā)并列一般是這樣配合使用的
//主隊(duì)列 + 全局并發(fā)隊(duì)列的日常使用
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//執(zhí)行耗時(shí)操作
dispatch_async(dispatch_get_main_queue(), ^{
//回到主線程進(jìn)行UI操作
});
});
函數(shù)與隊(duì)列的不同組合
串行隊(duì)列 + 同步函數(shù)
【任務(wù)按順序執(zhí)行】:任務(wù)一個(gè)接一個(gè)的在當(dāng)前線程執(zhí)行,不開辟新線程

串行隊(duì)列 + 異步函數(shù)
【任務(wù)按順序執(zhí)行】:任務(wù)一個(gè)接一個(gè)的執(zhí)行,會(huì)開辟新線程

并發(fā)隊(duì)列 + 同步函數(shù)
【任務(wù)按順序執(zhí)行】:任務(wù)一個(gè)接一個(gè)的執(zhí)行,不開辟線程

并發(fā)隊(duì)列 + 異步函數(shù)
【任務(wù)按亂序執(zhí)行】:任務(wù)執(zhí)行無順序,會(huì)開辟新線程

主隊(duì)列 + 同步函數(shù)
【造成死鎖】:任務(wù)相互等待,造成死鎖

造成死鎖的原因分析如下:
- 主隊(duì)列有兩個(gè)任務(wù),順序?yàn)椋?
NSLog任務(wù) - 同步block - 執(zhí)行NSLog任務(wù)后,執(zhí)行同步Block,會(huì)將任務(wù)1(即i=1時(shí))加入到主隊(duì)列,主隊(duì)列順序?yàn)椋?code>NSLog任務(wù) - 同步block - 任務(wù)1
-
任務(wù)1的執(zhí)行需要等待同步block執(zhí)行完畢才會(huì)執(zhí)行,而同步block的執(zhí)行需要等待任務(wù)1執(zhí)行完畢,所以就造成了任務(wù)互相等待的情況,即造成死鎖崩潰
死鎖現(xiàn)象
-
主線程因?yàn)?code>同步函數(shù)的原因等著先執(zhí)行任務(wù) -
主隊(duì)列等著主線程的任務(wù)執(zhí)行完畢再執(zhí)行自己的任務(wù) -
主隊(duì)列和主線程相互等待會(huì)造成死鎖
主隊(duì)列 + 異步函數(shù)
【任務(wù)按順序執(zhí)行】:任務(wù)一個(gè)接一個(gè)的執(zhí)行,不開辟線程

全局并發(fā)隊(duì)列 + 同步函數(shù)
【任務(wù)按順序執(zhí)行】:任務(wù)一個(gè)接一個(gè)的執(zhí)行,不開辟新線程

全局并發(fā)隊(duì)列 + 異步函數(shù)
【任務(wù)按亂序執(zhí)行】:任務(wù)亂序執(zhí)行,會(huì)開辟新線程

總結(jié)
| 函數(shù)隊(duì)列 | 串行隊(duì)列 | 并發(fā)隊(duì)列 | 主隊(duì)列 | 全局并發(fā)隊(duì)列 |
|---|---|---|---|---|
| 同步函數(shù) | 順序執(zhí)行,不開辟線程 | 順序執(zhí)行,不開辟線程 | 死鎖 | 順序執(zhí)行,不開辟線程 |
| 異步函數(shù) | 順序執(zhí)行,開辟線程 | 亂序執(zhí)行,開辟線程 | 順序執(zhí)行,不開辟線程 | 亂序執(zhí)行,開辟線程 |
相關(guān)面試題解析
【面試題 - 1】異步函數(shù)+并行隊(duì)列
下面代碼的輸出順序是什么?
- (void)interview01{
//并行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
// 耗時(shí)
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
----------打印結(jié)果-----------
輸出順序?yàn)椋? 5 2 4 3
-
異步函數(shù)并不會(huì)阻塞主隊(duì)列,會(huì)開辟新線程執(zhí)行異步任務(wù)

代碼分析
如下圖所示,紅線表示任務(wù)的執(zhí)行順序

主線程的任務(wù)隊(duì)列為:任務(wù)1、異步block1、任務(wù)5,其中異步block1會(huì)比較耗費(fèi)性能,任務(wù)1和任務(wù)5的任務(wù)復(fù)雜度是相同的,所以任務(wù)1和任務(wù)5優(yōu)先于異步block1執(zhí)行在
異步block1中,任務(wù)隊(duì)列為:任務(wù)2、異步block2、任務(wù)4,其中block2相對(duì)比較耗費(fèi)性能,任務(wù)2和任務(wù)4是復(fù)雜度一樣,所以任務(wù)2和任務(wù)4優(yōu)先于block2執(zhí)行最后執(zhí)行
block2中的任務(wù)3在極端情況下,可能出現(xiàn)
任務(wù)2先于任務(wù)1和任務(wù)5執(zhí)行,原因是出現(xiàn)了當(dāng)前主線程卡頓或者延遲的情況
代碼修改
【修改1】:將
并行隊(duì)列改成串行隊(duì)列,對(duì)結(jié)果沒有任何影響,順序仍然是 1 5 2 4 3【修改2】:在任務(wù)5之前,休眠2s,即
sleep(2),執(zhí)行的順序?yàn)椋? 2 4 3 5,原因是因?yàn)镮/O的打印,相比于休眠2s,復(fù)雜度更簡(jiǎn)單,所以異步block1 會(huì)先于任務(wù)5執(zhí)行。當(dāng)然如果主隊(duì)列堵塞,會(huì)出現(xiàn)其他的執(zhí)行順序
【面試題 - 2】異步函數(shù)嵌套同步函數(shù) + 并發(fā)隊(duì)列
下面代碼的輸出順序是什么?
- (void)interview02{
//并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
//異步函數(shù)
dispatch_async(queue, ^{
NSLog(@"2");
//同步函數(shù)
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
----------打印結(jié)果-----------
輸出順序?yàn)椋? 5 2 3 4
分析
任務(wù)1 和 任務(wù)5的分析同前面一致,執(zhí)行順序?yàn)?
任務(wù)1 任務(wù)5 異步block在異步block中,首先執(zhí)行 任務(wù)2 ,然后走到
同步block,由于同步函數(shù)會(huì)阻塞主線程,所以任務(wù)4需要等待任務(wù)3執(zhí)行完成后,才能執(zhí)行,所以異步block中的執(zhí)行順序是:任務(wù)2 任務(wù)3 任務(wù)4

面試題 - 3】異步函數(shù)嵌套同步函數(shù) + 串行隊(duì)列(即同步隊(duì)列)
下面代碼的執(zhí)行順序是什么?會(huì)出現(xiàn)什么情況?為什么?
- (void)interview03{
// 同步隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", NULL);
NSLog(@"1");
// 異步函數(shù)
dispatch_async(queue, ^{
NSLog(@"2");
// 同步函數(shù)
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
----------打印結(jié)果-----------
輸出順序?yàn)椋? 5 2 死鎖崩潰
分析
如下圖所示,紅色表示任務(wù)執(zhí)行順序,黑色虛線表示等待

首先執(zhí)行
任務(wù)1,接下來是異步block,并不會(huì)阻塞主線程,相比任務(wù)5而言,復(fù)雜度更高,所以優(yōu)先執(zhí)行任務(wù)5,在執(zhí)行異步block在
異步block中,先執(zhí)行任務(wù)2,接下來是同步block,同步函數(shù)會(huì)阻塞線程,所以執(zhí)行任務(wù)4需要等待任務(wù)3執(zhí)行完成,而任務(wù)3的執(zhí)行,需要等待異步block執(zhí)行完成,相當(dāng)于任務(wù)3等待任務(wù)4完成所以就造成了
任務(wù)4等待任務(wù)3,任務(wù)3等待任務(wù)4,即互相等待的局面,就會(huì)造成死鎖,這里有個(gè)重點(diǎn)是關(guān)鍵的堆棧 slow

修改
去掉任務(wù)4,執(zhí)行順序是什么?
- 還是會(huì)
死鎖,因?yàn)?code>任務(wù)3等待的是異步block執(zhí)行完畢,而異步block等待任務(wù)3
【面試題 - 4 - 新浪】 異步函數(shù) + 同步函數(shù) + 并發(fā)隊(duì)列
下面代碼的執(zhí)行順序是什么?(答案是 AC)
A: 1230789
B: 1237890
C: 3120798
D: 2137890
- (void)interview04{
//并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // 耗時(shí)
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"0");
dispatch_async(queue, ^{
NSLog(@"7");
});
dispatch_async(queue, ^{
NSLog(@"8");
});
dispatch_async(queue, ^{
NSLog(@"9");
});
}
----------打印結(jié)果-----------
輸出順序?yàn)椋海? 2 3 無序)0(7 8 9 無序),可以確定的是 0 一定在3之后,在789之前
分析
任務(wù)1 和 任務(wù)2由于是異步函數(shù)+并發(fā)隊(duì)列,會(huì)開啟線程,所以沒有固定順序
任務(wù)7、任務(wù)8、任務(wù)9同理,會(huì)開啟線程,所以沒有固定順序
任務(wù)3是同步函數(shù)+并發(fā)隊(duì)列,同步函數(shù)會(huì)阻塞主線程,但是也只會(huì)阻塞0,所以,可以確定的是 0一定在3之后,在789之前
以下是不同的執(zhí)行順序的打印

【面試題 - 5 - 美團(tuán)】下面代碼中,隊(duì)列的類型有幾種
//串行隊(duì)列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.CJL.Queue", NULL);
//并發(fā)隊(duì)列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
//主隊(duì)列 - Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局并發(fā)隊(duì)列 - Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
隊(duì)列總共有兩種: 并發(fā)隊(duì)列和 串行隊(duì)列
- 串行隊(duì)列:
serialQueue、mainQueue - 并發(fā)隊(duì)列:
concurrentQueue、globalQueue