一抱完、引言
前邊一篇文章我們已經(jīng)大致介紹了GCD的有些概念和函數(shù)的執(zhí)行。接下來(lái)讓我們繼續(xù)帶著探索的心里去學(xué)習(xí)刃泡,繼續(xù)前行巧娱,繼續(xù)介紹線程是如何開辟和創(chuàng)建的,又是什么時(shí)候去執(zhí)行相關(guān)的調(diào)度任務(wù)烘贴。接下來(lái)讓我們從相關(guān)的概念入手
二 禁添、線程和任務(wù)
2.1 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 程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼
2.2 任務(wù)
- 1 任務(wù)使用 block 封裝
- 2 任務(wù)的 block 沒有參數(shù)也沒有返回值
2.3 同步任務(wù)
- 1 函數(shù):
dispatch_sync
- 2 必須等待當(dāng)前語(yǔ)句執(zhí)行完畢,才會(huì)執(zhí)行下一條語(yǔ)句
- 3 不會(huì)開啟線程
- 4 在當(dāng)前執(zhí)行 block 的任務(wù)
2.4異步任務(wù)
- 1 異步
dispatch_async
- 2 不用等待當(dāng)前語(yǔ)句執(zhí)行完畢铺峭,就可以執(zhí)行下一條語(yǔ)句
- 3 會(huì)開啟線程執(zhí)行 block 的任務(wù)
- 4 異步是多線程的代名詞
2.5主隊(duì)列
- 1 函數(shù):
dispatch_get_main_queue()
; - 2 特殊的串行隊(duì)列
- 3專門用來(lái)在主線程上調(diào)度任務(wù)的隊(duì)列
- 4不會(huì)開啟線程
- 5 如果當(dāng)前主線程正在執(zhí)行任務(wù)墓怀,那么無(wú)論主隊(duì)列中當(dāng)前被添加了什么任務(wù),都不會(huì)被調(diào)度
2.6.全局隊(duì)列
- 1 函數(shù):
dispatch_get_global_queue(0,0)
; - 2 為了方便程序員使用卫键,蘋果提供了全局隊(duì)列
- 3全局隊(duì)列是一個(gè)并發(fā)隊(duì)列
- 4 在使用多線程開發(fā)時(shí)傀履,如果對(duì)隊(duì)列沒有特殊需求,在執(zhí)行異步任務(wù)時(shí)莉炉,可以直接使用全局隊(duì)列
2.7 隊(duì)列和任務(wù)搭配
- 1 同步串行隊(duì)列:不開啟新線程,串行執(zhí)行任務(wù);
- 2 同步并發(fā)隊(duì)列:不開啟新線程,串行執(zhí)行任務(wù);
- 3同步主隊(duì)列:如果在主線程調(diào)用,造成死鎖;如果在其他線程調(diào)用,不開啟新線程,串行執(zhí)行,在主線程串行執(zhí)行任務(wù);
- 4 異步串行隊(duì)列:開啟新線程,串行執(zhí)行任務(wù);
- 5 異步并發(fā)隊(duì)列:開啟新線程,并發(fā)執(zhí)行任務(wù);
- 6 異步主隊(duì)列:不開啟新線程,在主線程串行執(zhí)行任務(wù)
- 7 死鎖:
三钓账、GCD 底層源碼分析
3.1 dispatch_async(異步函數(shù))在何時(shí)開辟線程
我們都知道主線程和同步線程不會(huì)開辟子線程,但是當(dāng)我們用dispatch_async
(異步函數(shù))的時(shí)候絮宁,線程是如何開辟的呢梆暮?是何時(shí)執(zhí)行的調(diào)度任務(wù)呢?這些都是我們?cè)谑褂肎CD的情況下很少去考慮的問題绍昂,為了弄清楚相關(guān)的原理啦粹,讓我們?cè)敿?xì)去研究一下相關(guān)的流程。
我們?cè)跍y(cè)試Demo中打印一個(gè)簡(jiǎn)單的語(yǔ)句治专,并且進(jìn)行相應(yīng)的斷點(diǎn)調(diào)試卖陵,然后打印出相應(yīng)的堆棧信息
dispatch_async(_queue, ^{
NSLog(@"1111111");
})
在控制臺(tái)進(jìn)行bt
打印所有的堆棧信息,可以得出相應(yīng)的執(zhí)行步驟如下
從打印結(jié)果看我們知道異步線程會(huì)執(zhí)行_dispatch_lane_invoke
,有興趣的同學(xué)可以加一個(gè)符號(hào)斷點(diǎn)進(jìn)行調(diào)試张峰,程序一定會(huì)執(zhí)行到此為止的泪蔫,我們進(jìn)入相應(yīng)的定義
void
_dispatch_lane_invoke(dispatch_lane_t dq, dispatch_invoke_context_t dic,
dispatch_invoke_flags_t flags)
{
_dispatch_queue_class_invoke(dq, dic, flags, 0, _dispatch_lane_invoke2);
}
然后到_dispatch_lane_serial_drain
的定義
dispatch_queue_wakeup_target_t
_dispatch_lane_serial_drain(dispatch_lane_class_t dqu,
dispatch_invoke_context_t dic, dispatch_invoke_flags_t flags,
uint64_t *owned)
{
flags &= ~(dispatch_invoke_flags_t)DISPATCH_INVOKE_REDIRECTING_DRAIN;
return _dispatch_lane_drain(dqu._dl, dic, flags, owned, true);
}
最終會(huì)走到_dispatch_root_queue_poke_slow
方法,進(jìn)行相應(yīng)的線程創(chuàng)建喘批,
其中創(chuàng)建線程的代碼如下
#if !defined(_WIN32)
pthread_attr_t *attr = &pqc->dpq_thread_attr;
pthread_t tid, *pthr = &tid;
#if DISPATCH_USE_MGR_THREAD && DISPATCH_USE_PTHREAD_ROOT_QUEUES
if (unlikely(dq == &_dispatch_mgr_root_queue)) {
pthr = _dispatch_mgr_root_queue_init();
}
#endif
do {
_dispatch_retain(dq); // released in _dispatch_worker_thread
while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) {
if (r != EAGAIN) {
(void)dispatch_assume_zero(r);
}
_dispatch_temporary_resource_shortage();
}
} while (--remaining);
#else // defined(_WIN32)
#if DISPATCH_USE_MGR_THREAD && DISPATCH_USE_PTHREAD_ROOT_QUEUES
if (unlikely(dq == &_dispatch_mgr_root_queue)) {
_dispatch_mgr_root_queue_init();
}
從最終的線程創(chuàng)建我們知道撩荣,線程創(chuàng)建過程當(dāng)中,不斷的判斷需要?jiǎng)?chuàng)建多少線程饶深,每創(chuàng)建一個(gè)就減少一個(gè)餐曹,從而調(diào)用pthread_create
的API進(jìn)行創(chuàng)建。這就是GCD
線程創(chuàng)建的過程敌厘;
3.2 dispatch_barrier_async(柵欄函數(shù))
dispatch_barrier_async
(柵欄函數(shù))他台猴,通過其命名我們就知道是攔截的意思。也就是在柵欄函數(shù)之前的任務(wù)執(zhí)行完成后俱两,才能執(zhí)行后邊的任務(wù)饱狂。先看一下官方文檔
dispatch_barrier_sync
: Submits a barrier block object for execution and waits until that block completes.(提交一個(gè)柵欄函數(shù)在執(zhí)行中,它會(huì)等待柵欄函數(shù)執(zhí)行完)
dispatch_barrier_async
: Submits a barrier block for asynchronous execution and returns immediately.(提交一個(gè)柵欄函數(shù)在異步執(zhí)行中,它會(huì)立馬返回)
作者理解:dispatch_barrier_sync
需要等待柵欄執(zhí)行完才會(huì)執(zhí)行柵欄后面的任務(wù),而dispatch_barrier_async
無(wú)需等待柵欄執(zhí)行完,會(huì)繼續(xù)往下走(保留在隊(duì)列里)
原因:在同步柵欄時(shí)柵欄函數(shù)在主線程中執(zhí)行,而異步柵欄中開辟了子線程?hào)艡诤瘮?shù)在子線程中執(zhí)行
我們看看dispatch_barrier_async
異步柵欄函數(shù)的執(zhí)行結(jié)果
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"123");
});
/* 2. 柵欄函數(shù) */ // - dispatch_barrier_sync
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
});
/* 3. 異步函數(shù) */
dispatch_async(concurrentQueue, ^{
NSLog(@"加載那么多,喘口氣!!!");
});
NSLog(@"**********起來(lái)干!!");
執(zhí)行結(jié)果是
我們?cè)俅慰纯?code>dispatch_barrier_sync同步柵欄函數(shù)的執(zhí)行結(jié)果:
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"123");
});
/* 2. 柵欄函數(shù) */ // - dispatch_barrier_sync
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
});
/* 3. 異步函數(shù) */
dispatch_async(concurrentQueue, ^{
NSLog(@"加載那么多,喘口氣!!!");
});
NSLog(@"**********起來(lái)干!!");
打印結(jié)果如下結(jié)論 dispatch_barrier_sync
需要等待柵欄執(zhí)行完才會(huì)執(zhí)行柵欄后面的任務(wù),而dispatch_barrier_async
無(wú)需等待柵欄執(zhí)行完,會(huì)繼續(xù)往下走(保留在隊(duì)列里)
3.3 dispatch_semaphore_t(信號(hào)量)
信號(hào)量是通過信號(hào)的數(shù)量來(lái)控制相應(yīng)的任務(wù)調(diào)度,需要設(shè)置一個(gè)固定的信號(hào)量宪彩,有可以操作的任務(wù)調(diào)度才可以執(zhí)行休讳,就好比停車場(chǎng)的空車位數(shù)一樣,數(shù)量一定尿孔,如果有空就可以進(jìn)行停車俊柔,如果車位已滿就讓后來(lái)的人等待筹麸。其中信號(hào)量控制有三個(gè)非常重要的函數(shù)
dispatch_semaphore_create
dispatch_semaphore_wait
dispatch_semaphore_signal
1
dispatch_semaphore_create(long value)
;和GCD的group等用法一致,這個(gè)函數(shù)是創(chuàng)建一個(gè)dispatch_semaphore_類型的信號(hào)量雏婶,并且創(chuàng)建的時(shí)候需要指定信號(hào)量的大小物赶。2
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
; 等待信號(hào)量。該函數(shù)會(huì)對(duì)信號(hào)量進(jìn)行減1操作尚骄。如果減1后信號(hào)量小于0(即減1前信號(hào)量值為0)块差,那么該函數(shù)就會(huì)一直等待,也就是不返回(相當(dāng)于阻塞當(dāng)前線程)倔丈,直到該函數(shù)等待的信號(hào)量的值大于等于1憨闰,該函數(shù)會(huì)對(duì)信號(hào)量的值進(jìn)行減1操作,然后返回需五。3
dispatch_semaphore_signal(dispatch_semaphore_t deem)
; 發(fā)送信號(hào)量鹉动。該函數(shù)會(huì)對(duì)信號(hào)量的值進(jìn)行加1操作。
通常等待信號(hào)量和發(fā)送信號(hào)量的函數(shù)是成對(duì)出現(xiàn)的宏邮。并發(fā)執(zhí)行任務(wù)時(shí)候泽示,在當(dāng)前任務(wù)執(zhí)行之前,用dispatch_semaphore_wait
函數(shù)進(jìn)行等待(阻塞)蜜氨,直到上一個(gè)任務(wù)執(zhí)行完畢后且通過dispatch_semaphore_signal
函數(shù)發(fā)送信號(hào)量(使信號(hào)量的值加1)械筛,dispatch_semaphore_wait
函數(shù)收到信號(hào)量之后判斷信號(hào)量的值大于等于1,會(huì)再對(duì)信號(hào)量的值減1飒炎,然后當(dāng)前任務(wù)可以執(zhí)行埋哟,執(zhí)行完畢當(dāng)前任務(wù)后,再通過dispatch_semaphore_signal函數(shù)發(fā)送信號(hào)量(使信號(hào)量的值加1)郎汪,通知執(zhí)行下一個(gè)任務(wù)......如此一來(lái)赤赊,通過信號(hào)量,就達(dá)到了并發(fā)隊(duì)列中的任務(wù)同步執(zhí)行的要求煞赢。
dispatch_semaphore_signal底層實(shí)現(xiàn)過程
dispatch_semaphore_signal
的內(nèi)部實(shí)現(xiàn)是
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
long value = os_atomic_inc2o(dsema, dsema_value, release);
if (likely(value > 0)) {
return 0;
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
到 os_atomic_inc2o(dsema, dsema_value, release);
起底層定義是
#define os_atomic_inc2o(p, f, m) \
os_atomic_add2o(p, f, 1, m)
也就是調(diào)用了os_atomic_add2o
的函數(shù)封裝抛计;其內(nèi)部實(shí)現(xiàn)如下
#define os_atomic_add2o(p, f, v, m) \
os_atomic_add(&(p)->f, (v), m)
也就是os_atomic_add
其內(nèi)部定義封裝是
#define os_atomic_add(p, v, m) \
_os_atomic_c11_op((p), (v), m, add, +)
再次到_os_atomic_c11_op
函數(shù)的實(shí)現(xiàn),定義如下
最終我們知道其內(nèi)部是調(diào)用了atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p)
而其中##o##
是形參照筑,替換成第三個(gè)參數(shù)就是add
得到的結(jié)果就是atomic_fetch_add_explicit
,我們百度搜索就知道
原子替換指向的值
obj
和添加arg
到舊值的結(jié)果obj
吹截,并返回obj先前保存的值。操作是讀取 - 修改 - 寫入操作凝危。第一個(gè)版本根據(jù)命令對(duì)內(nèi)存進(jìn)行訪問memory_order_seq_cst
饭弓,第二個(gè)版本根據(jù)內(nèi)存訪問內(nèi)存訪問order
具體鏈接
同理,dispatch_semaphore_wait
也是一樣的原理過程媒抠。這就是信號(hào)量的底層實(shí)現(xiàn)邏輯。
3.4 dispatch_once_t(單利)的底層實(shí)現(xiàn)和邏輯
單利的底層實(shí)現(xiàn)代碼定義如下
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
dispatch_once_gate_t l = (dispatch_once_gate_t)val;
#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
if (likely(v == DLOCK_ONCE_DONE)) {
return;
}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
if (likely(DISPATCH_ONCE_IS_GEN(v))) {
return _dispatch_once_mark_done_if_quiesced(l, v);
}
#endif
#endif
if (_dispatch_once_gate_tryenter(l)) {
return _dispatch_once_callout(l, ctxt, func);
}
return _dispatch_once_wait(l);
}
單利的保證線程安全過程
根據(jù)以上代碼可知咏花,
- 1 我們可以知道外部傳入的一個(gè)任務(wù)值
val
趴生,單利內(nèi)部會(huì)對(duì)這個(gè)任務(wù)值進(jìn)行一個(gè)處理阀趴,處理成一個(gè)dispatch_once_gate_t
對(duì)象l
, - 2 通過內(nèi)核加載過程,看看當(dāng)前任務(wù)十分已經(jīng)被標(biāo)記過是需要執(zhí)行的任務(wù)苍匆;
- 3 如果是被標(biāo)記過的任務(wù)就直接執(zhí)行當(dāng)前任務(wù)刘急,并且對(duì)當(dāng)前任務(wù)的執(zhí)行流程加入一個(gè)鎖操作;
- 4 如果沒有標(biāo)記過浸踩,就對(duì)改任務(wù)進(jìn)行標(biāo)記叔汁,并且返回一個(gè)標(biāo)識(shí);
- 5 最后通過
CPU
調(diào)度检碗,選擇已經(jīng)標(biāo)識(shí)過的任務(wù)進(jìn)行執(zhí)行_dispatch_once_callout
- 6 同時(shí)有多個(gè)任務(wù)來(lái)就需要等待當(dāng)前任務(wù)的處理据块,等到處理過后進(jìn)行標(biāo)識(shí)才能從新調(diào)用;
四折剃、總結(jié)
以上就是GCD相關(guān)函數(shù)的底層實(shí)現(xiàn)過程以及一些原理的介紹另假,僅僅憑借個(gè)人的學(xué)習(xí)經(jīng)驗(yàn)和代碼閱讀過程以及Demo的調(diào)試得到的感觸,所以很多東西只是個(gè)人的理解怕犁,如果大神們有更好的思路和意見边篮,歡迎多多指正。我一定虛心學(xué)習(xí)奏甫,追求大神們的步伐戈轿。