GCD簡介
- GCD全稱:Grand Central Dispatch
- GCD是純C語言,提供了非常多的強(qiáng)大函數(shù)
- GCD是非常高效的多線程開發(fā)方式,它并不是Cocoa框架的一部分
GCD優(yōu)勢(shì)
- 1.GCD 是蘋果公司為多核的并?運(yùn)算提出的解決?案
- 2.GCD 會(huì)?動(dòng)利?更多的CPU內(nèi)核(?如雙核、四核)
- 3.GCD 會(huì)?動(dòng)管理線程的?命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
- 4.開發(fā)者只需要告訴 GCD 想要執(zhí)?什么任務(wù),不需要編寫任何線程管理代碼
【總結(jié)】:GCD就是將任務(wù)添加到隊(duì)列,并且指定執(zhí)行任務(wù)的函數(shù)。
GCD使用
在GCD使用中我們只需要做兩件事:1.定義任務(wù)。2.將任務(wù)添加到隊(duì)列中。所以GCD的核心就是dispatch隊(duì)列和任務(wù)。
GCD隊(duì)列
下面是GCD獲取隊(duì)列的集中方式:
- 1.主線程隊(duì)列:提交的任務(wù)將會(huì)在主線程完成
- 可以通過dispatch_get_main_queue()來獲得。
- 主隊(duì)列就是主線程,它是一個(gè)串行隊(duì)列,在iOS中只有主線程才能擁有權(quán)限向渲染服務(wù)提交圖層信息,完成圖形顯示工作。所以和UI相關(guān)操作,必須在主線程執(zhí)行。
- 2.全局并發(fā)隊(duì)列(Clobal Queue):全局并發(fā)隊(duì)列由整個(gè)進(jìn)程共享,有高、中(默認(rèn))、低、后臺(tái)四個(gè)優(yōu)先級(jí)
- 3.自定義隊(duì)列
- 并發(fā)隊(duì)列:
- 全局隊(duì)列是并發(fā)隊(duì)列
- 通過dispatch_queue_create創(chuàng)建,第二個(gè)參數(shù)賦值為DISPATCH_QUEUE_CONCURRENT等
- 不用等待上個(gè)任務(wù)是否完成,直接啟用新的線程執(zhí)行新的任務(wù)。
- 串行隊(duì)列:
- 通過dispatch_queue_create創(chuàng)建,第二個(gè)參數(shù)賦值為DISPATCH_QUEUE_SERIAL或者NULL。
- 串行隊(duì)列在同一時(shí)間只能執(zhí)行一個(gè)任務(wù)
- 并發(fā)隊(duì)列:

GCD任務(wù)
GCD任務(wù)就是操作意思,就是你在block塊中的代碼通過什么方式執(zhí)行。執(zhí)行任務(wù)有兩種方式:同步和異步,兩者主要區(qū)別是:是否等待隊(duì)列的任務(wù)執(zhí)行結(jié)束,以及是否具備開辟線程的能力。
同步執(zhí)行(sync)
- 1.同步添加任務(wù)到指定的隊(duì)列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會(huì)一直等待,直到隊(duì)列里面的任務(wù)完成之后再繼續(xù)執(zhí)行。
- 2.只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力。
異步執(zhí)行(async)
- 1.異步添加任務(wù)到指定的隊(duì)列中,它不會(huì)做任何等待,可以繼續(xù)執(zhí)行任務(wù)。
- 2.可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力。

下面我們?cè)賹㈥?duì)列和任務(wù)搭配執(zhí)行看看打印結(jié)果,準(zhǔn)備代碼
/**
同步并發(fā)
*/
- (void)concurrentSyncTest{
dispatch_queue_t queue = dispatch_queue_create("LJ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10; i++) {
dispatch_sync(queue, ^{
NSLog(@"同步并發(fā)-%d-%@",i,[NSThread currentThread]);
});
}
}
/**
異步并發(fā)
*/
- (void)concurrentAsyncTest{
dispatch_queue_t queue = dispatch_queue_create("LJ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"異步并發(fā)-%d-%@",i,[NSThread currentThread]);
});
}
}
/**
串行異步
*/
- (void)serialAsyncTest{
dispatch_queue_t queue = dispatch_queue_create("LJ", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"串行異步-%d-%@",i,[NSThread currentThread]);
});
}
}
/**
串行同步
*/
- (void)serialSyncTest{
dispatch_queue_t queue = dispatch_queue_create("LJ", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<10; i++) {
dispatch_sync(queue, ^{
NSLog(@"串行同步-%d-%@",i,[NSThread currentThread]);
});
}
}




通過任務(wù)執(zhí)行方式和不同隊(duì)列組合,我們通過打印信息可以得出如下結(jié)論:
- 1.任務(wù)執(zhí)行方式是
異步或者同步只能決定是否開辟新的線程。同步(不開辟線程),異步(開辟新的線程) - 2.隊(duì)列是
并行還是串行只能決定是否開辟多條線程。串行(只開辟一條線程),并行(開辟多條線程,開辟多條線程的能力只有在異步執(zhí)行中發(fā)揮作用) - 3.
異步并行執(zhí)行任務(wù)是亂序的。
死鎖
造成死鎖的主要原因就是任務(wù)相互等待,看下面代碼:

發(fā)現(xiàn)報(bào)錯(cuò)了,報(bào)錯(cuò)原因就是死鎖。下面我們分析下為什么會(huì)死鎖: 這個(gè)方法有3步操作:
- 任務(wù)一:132行打印1任務(wù),此部分在主線程。
- 任務(wù)二:137行打印3任務(wù)
- 任務(wù)三:134-136行通過同步任務(wù)向主線程插入打印2任務(wù)
我們知道主線程是同步任務(wù),任務(wù)一和任務(wù)二是先加入主線程,任務(wù)三會(huì)排在任務(wù)一,二后面。但是任務(wù)三是通過同步任務(wù)加入的。這就會(huì)出現(xiàn)下面的情況,任務(wù)三需要等待主線程執(zhí)行完任務(wù)一,二后才會(huì)執(zhí)行。而同步任務(wù)的出現(xiàn)會(huì)讓任務(wù)二等待任務(wù)三執(zhí)行完成后才執(zhí)行,這就造成了在主線程中任務(wù)三等待任務(wù)二完成執(zhí)行,在同步任務(wù)里出現(xiàn)任務(wù)二等待任務(wù)三完成執(zhí)行,這就造成了相互等待。出現(xiàn)死鎖崩潰 如下圖所示更容易解釋:

GCD原理初探
上面我們說了GCD的任務(wù)和隊(duì)列,并通過代碼打印來說明了任務(wù)和隊(duì)列的關(guān)系,線面我們就來看看GCD的底層實(shí)現(xiàn)
確定GCD研究源碼位置
我們想要研究GCD,卻發(fā)現(xiàn)不知從哪入手,代碼點(diǎn)擊進(jìn)去之后就走不下去了。那么我們?cè)趺粗谰€程這部分的源碼在哪呢?我們要確定源碼,我們知道dispatch_queue_create方法可以創(chuàng)建線程,那么我們打斷點(diǎn)試試


這時(shí)就可以確定線程的源碼在libdispatch.dylib中。我們?cè)谔O果的官方文檔上下載libdispatch.dylib源碼。
dispatch_get_main_queue()初探
我們先看dispatch_get_main_queue()主線程

下圖是對(duì)主線程的解釋(撿主要的說一下):
- 569-570行:
主隊(duì)列是用來在應(yīng)用程序上下文中進(jìn)行交互的主線程和主runloop。 - 579-580行:
主隊(duì)列會(huì)被自動(dòng)創(chuàng)建,而且會(huì)在main()函數(shù)之前創(chuàng)建
在main()函數(shù)前被調(diào)用,就是在dyld過程中進(jìn)行的
dispatch_get_main_queue()再探
我們打印下主線程,來看看主線程是什么樣的
dispatch_queue_t serial = dispatch_queue_create("Lj", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t conque = dispatch_queue_create("Lj", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
NSLog(@"%@-%@-%@-%@",serial,conque,mainQueue,globQueue);
運(yùn)行打印
我們通過打印結(jié)果看到主線程變?yōu)榱薕S_dispatch_queue_main: com.apple.main-thread,說明在底層系統(tǒng)進(jìn)行命名為com.apple.main-thread。我們?cè)趌ibdispatch源碼里搜索com.apple.main-thread看看


共有8個(gè)文件,42個(gè)地方出現(xiàn)
下面那么我們應(yīng)該怎么辦?
libdispatch_init
多線程的調(diào)用最早是創(chuàng)建,我們?cè)?code>講dyld的加載是提到過線程的加載:libdispatch_init OC底層原理之-dyld加載流程傳從門,搜索libdispatch_init

我們看到libdispatch_init方法很多,我們說主要方法,看下7759行代碼,我們上面說的靜態(tài)結(jié)構(gòu)體_dispatch_main_q的do_targetq等于_dispatch_get_default_queue(true)。后面就是對(duì)_dispatch_main_q進(jìn)行一系列的操作(7762行:設(shè)置當(dāng)前的主隊(duì)列,7763行:綁定到相應(yīng)的線程)。下面我們查看下綁定過程:_dispatch_queue_set_bound_thread。

通過上圖源碼我們可以看到,綁定的底層實(shí)現(xiàn)是通過os_atomic_rmw_loop2o方法處理的,這部分實(shí)不在libdispatch源碼中,后續(xù)有機(jī)會(huì)我們?cè)傺芯俊?/p>
總結(jié)
主線程下層是_dispatch_main_q的結(jié)構(gòu)體,它是在dyly加載中通過libdispatch_init方法進(jìn)行創(chuàng)建,它是一個(gè)相當(dāng)于串行隊(duì)列的隊(duì)列。
dispatch_get_global_queue
我們點(diǎn)擊去看下:
dispatch_get_global_queue需要傳入兩個(gè)參數(shù):identifier和flags,注釋對(duì)這兩個(gè)參數(shù)進(jìn)行了說明:
- identifier:服務(wù)質(zhì)量(優(yōu)先級(jí))
- flags:預(yù)留使用
因?yàn)?code>存在優(yōu)先級(jí),就說明整個(gè)項(xiàng)目中可以有多個(gè)dispatch_get_global_queue,那么如何去設(shè)計(jì)它呢?我們可以想到通過集合去收集dispatch_get_global_queue,下面我們通過com.apple.root.default-qos來查找一下dispatch_get_global_queue全局隊(duì)列。

上圖發(fā)現(xiàn)都是通過_DISPATCH_ROOT_QUEUE_ENTRY方法去創(chuàng)建的。這里面有各種各樣不同優(yōu)先級(jí)的全局并發(fā)隊(duì)列。
我們?cè)俨榭串?dāng)前的結(jié)構(gòu)體為_dispatch_root_queues,它也是一個(gè)靜態(tài)結(jié)構(gòu)體。
隊(duì)列如何創(chuàng)建,DISPATCH_QUEUE_SERIAL和DISPATCH_QUEUE_CONCURRENT區(qū)別
上面我們簡單的講了下dispatch_get_main_queue()和dispatch_get_global_queue,知道他們底層是靜態(tài)結(jié)構(gòu)體。下面我們主要講下隊(duì)列的創(chuàng)建,以及串行和并行的實(shí)現(xiàn)原理
dispatch_queue_create
看下底層實(shí)現(xiàn)
我們搜索_dispatch_lane_create_with_target看下其內(nèi)部實(shí)現(xiàn)發(fā)現(xiàn)dispatch_queue_create是通過_dispatch_lane_create_with_target創(chuàng)建的,參數(shù)分別為label以及attr,后面的DISPATCH_TARGET_QUEUE_DEFAULT、true是默認(rèn)值




我們看到
_dispatch_introspection_queue_create方法傳入的是dq,先創(chuàng)建dqic(653行創(chuàng)建,654行將dqic的dqic_queue._dq賦值為dq)。659行又將dq的do_finalizer賦值為dqic。之后就返回了upcast(dq)的_dqu。
上面并沒有我們想要的東西。我們回到_dispatch_lane_create_with_target方法,再看返回值return _dispatch_trace_queue_create(dq)._dq;這個(gè)方法返回的是_dq,上面我們知道_dispatch_introspection_queue_create返回的dq._dq中的dq是進(jìn)行賦值,和傳入的dq其實(shí)是同一個(gè)。我們只需要研究_dispatch_lane_create_with_target傳入的dq就可以了。


我們看到init方法里我們看到dqai.dqai_concurrent的屬性,這個(gè)屬性對(duì)線程的影響

下面我們看下dqai的創(chuàng)建我們看到
dqai.dqai_concurrent確定的值就是width,1172行DOF_WIDTH()就是該隊(duì)列支持的線程數(shù),1就是串行(單線程),>1就是并行(多線程),也就是如果dqai.dqai_concurrent為true就是多線程,否則為單線程。



這個(gè)截圖是如果dqa==&_dispatch_queue_attr_concurrent就為true就是多線程。
我們?cè)倩氐絖dispatch_lane_create_with_target方法






我們發(fā)現(xiàn)串行和并行打印的結(jié)果和上面推測(cè)的一致。
我們?cè)倩氐絖dispatch_lane_create_with_target方法,繼續(xù)看_dispatch_object_alloc方法。


我們是onjc2所以會(huì)走_(dá)os_object_alloc_realized方法,上面我們已經(jīng)知道vtable在串行和并行賦值不同,在_os_object_alloc_realized中vtable值就是cls,1509行:創(chuàng)建是將isa指針指向了cls也就是指向了vtable。
總結(jié)
隊(duì)列創(chuàng)建底層是_dispatch_lane_create_with_target創(chuàng)建,通過傳入的值來確定是串行還是并行隊(duì)列,dispatch_queue_t也是個(gè)對(duì)象,也會(huì)通過alloc,init進(jìn)行創(chuàng)建。在alloc中將isa指針指向并發(fā)還是串行,通過init來確定DOF_WIDTH()等屬性。
dispatch_async
上面講了隊(duì)列的創(chuàng)建,下面我們看下異步任務(wù)的實(shí)現(xiàn)
- dq:就是穿過進(jìn)來的隊(duì)列
- work:就是傳進(jìn)來的任務(wù)
我們看下代碼怎么操作的
- 890行:創(chuàng)建dc
- 896行:任務(wù)包裝器,用來接收,保存block

2633-2638行:將work保存在dc的dc_ctxt中,其實(shí)這個(gè)判斷是不走的,會(huì)走下面,
我們看下_dispatch_continuation_init_f,重點(diǎn)關(guān)注ctxt(將work進(jìn)行copy)func(將work進(jìn)行調(diào)用)。注意:func在2642行執(zhí)行了方法,也就是func執(zhí)行完后會(huì)進(jìn)行析構(gòu)或者釋放。

此時(shí)我們看到上面參數(shù)
對(duì)應(yīng)的就是ctxt和f。方法將ctxt和f分別保存到dc的dc_ctxt和dc_func屬性中。
探究dispatch_async中work的執(zhí)行
從上面知道work就是任務(wù),我們探究下

我們看到start_wqthread,_pthread_wqthread是在libsystem_pthread源碼中,而libdispatch源碼中走的第一個(gè)方法就是_dispatch_worker_thread2。我們搜索一下
_dispatch_worker_thread2





最后會(huì)調(diào)用_dispatch_continuation_init方法中的_dispatch_call_block_and_release最后
調(diào)用的是f(ctxt)方法,我們?cè)谏厦?code>講dispatch_async的_dispatch_continuation_init_f方法說了,最后會(huì)將調(diào)用任務(wù)方法放在f中,將任務(wù)放在ctxt中,此處得到驗(yàn)證。

這就是block任務(wù)執(zhí)行的整個(gè)流程。
拓展
相關(guān)面試題
【面試題 - 1】異步函數(shù)+并行隊(duì)列 下面打印結(jié)果是什么?
- (void)textDemo2{
dispatch_queue_t queue = dispatch_queue_create("Lj", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
// 異步函數(shù)
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
答案:1,5,2,4,3
解題:上面講了,queue為并發(fā)隊(duì)列,不會(huì)阻塞線程,所以1,5先執(zhí)行。而并行隊(duì)列里包含并行隊(duì)列,所以他們?nèi)蝿?wù)互不影響。所以2,4先打印,最后為3。
代碼修改
【修改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ù)雜度更簡單,所以異步block1 會(huì)先于任務(wù)5執(zhí)行。當(dāng)然如果主隊(duì)列堵塞,會(huì)出現(xiàn)其他的執(zhí)行順序。
【修改3】:將打印 NSLog(@"3");的異步dispatch_async,改為同步dispatch_sync。執(zhí)行順序是:1,5,2,3,4,原因:將之前的異步改為同步夠,會(huì)阻塞打印2的線程,導(dǎo)致只有打印3執(zhí)行完后才能執(zhí)行打印4。
【面試題 - 2】異步函數(shù)嵌套同步函數(shù) + 串行隊(duì)列(即同步隊(duì)列)
- (void)textDemo2{
// 同步隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("Lj", NULL);
NSLog(@"1");
// 異步函數(shù)
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
答案:1,5,2崩潰 [圖片上傳失敗...(image-338b41-1628941505662)]
原因:queue是串行隊(duì)列,1,5,2正常打印不再解釋,執(zhí)行完2后(2當(dāng)前線程為串行),打印3任務(wù)通過同步任務(wù)插入到串行隊(duì)列,放在打印4的后面(在執(zhí)行2串行任務(wù)里,4的打印在3的前面),但是同步任務(wù)有需要先執(zhí)行3在執(zhí)行4,就造成相互等待,造成死鎖。
【修改】:將打印4去掉呢?
- 還是會(huì)死鎖,因?yàn)槿蝿?wù)3等待的是異步block執(zhí)行完畢,而異步block等待任務(wù)3執(zhí)行完成,還是會(huì)相互等待,造成死鎖
【面試題 - 3】 異步函數(shù) + 同步函數(shù) + 并發(fā)隊(duì)列
下面代碼的執(zhí)行順序是什么?(答案是 AC)
- (void)interview04{
//并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("Lj", 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");
});
}
A: 1230789
B: 1237890
C: 3120798
D: 2137890
答案:AC
- 1.任務(wù)1 和 任務(wù)2由于是異步函數(shù)+并發(fā)隊(duì)列,會(huì)開啟線程,所以沒有固定順序
- 2.任務(wù)7、任務(wù)8、任務(wù)9同理,會(huì)開啟線程,所以沒有固定順序
- 3.任務(wù)3是同步函數(shù)+并發(fā)隊(duì)列,同步函數(shù)會(huì)阻塞主線程,但是也只會(huì)阻塞0,所以,可以確定的是 0一定在3之后,在789之前
【面試題 - 4】下面代碼中,隊(duì)列的類型有幾種?
//串行隊(duì)列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("Lj", NULL);
//并發(fā)隊(duì)列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("Lj", 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);
答案:1.串行隊(duì)列:serialQueue,mainQueue 2.并發(fā)隊(duì)列:concurrentQueue,globalQueue