異步函數(shù)
先看下dispatch_async的底層實(shí)現(xiàn)上圖我們發(fā)現(xiàn)有兩個(gè)主要方法:
- 1._dispatch_continuation_init這個(gè)方法上篇最后講了用處:就是
任務(wù)包裝渐夸,將work(任務(wù)執(zhí)行)綁定到dc的dc_ctxt中找颓,將方法綁定到dc的dc_func中
。 - 2._dispatch_continuation_async是
并發(fā)處理函數(shù)
,主要執(zhí)行block回調(diào)
侨嘀。
_dispatch_continuation_init方法上篇講了,這里不再過(guò)多陳述。我們看下_dispatch_continuation_async方法侥钳。
_dispatch_continuation_async
我們進(jìn)入_dispatch_continuation_async的方法實(shí)現(xiàn)方法中的關(guān)鍵代碼為2659行:
dx_push(dqu._dq, dc, qos)
;(原因:這個(gè)方法的結(jié)果作為返回值返回涡相,前面只是對(duì)dqu進(jìn)行處理
)哲泊。
dx_push
dx_push是個(gè)宏定義,如下圖所示:我們看到dx_push最后會(huì)
執(zhí)行dq_push方法
催蝗,而dq_push方法進(jìn)行搜索時(shí)發(fā)現(xiàn)有很多
切威。
先打斷點(diǎn):通過(guò)上圖我們可以看到,
.dq_push會(huì)根據(jù)隊(duì)列的不同類型丙号,執(zhí)行不同的函數(shù)
先朦。我們注意下673-682行,662-671行
犬缨,我們看到.do_type分別為DISPATCH_QUEUE_CONCURRENT_TYPE和DISPATCH_QUEUE_SERIAL_TYPE
喳魏,類似并發(fā)隊(duì)列和串行隊(duì)列
。我們通過(guò)發(fā)號(hào)斷點(diǎn)看看并發(fā)隊(duì)列是不是走_(dá)dispatch_lane_concurrent_push
遍尺,串行隊(duì)列是不是走_(dá)dispatch_lane_push
截酷。
dispatch_queue_t conque = dispatch_queue_create("Lj", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conque, ^{
NSLog(@"12334");
});
運(yùn)行代碼,在dispatch_async處打斷點(diǎn)乾戏,運(yùn)行到斷點(diǎn)處,在講符號(hào)斷點(diǎn)打開(kāi)三热,下一步鼓择,發(fā)現(xiàn)確實(shí)走到了_dispatch_lane_concurrent_push方法
。
下面我們看下串行隊(duì)列就漾。
dispatch_queue_t serial = dispatch_queue_create("Lj", DISPATCH_QUEUE_SERIAL);
dispatch_async(serial, ^{
NSLog(@"12334");
});
和驗(yàn)并行一樣呐能,發(fā)現(xiàn)確實(shí)執(zhí)行了_dispatch_lane_push方法
。
上面驗(yàn)證說(shuō)明了.dq_push確實(shí)會(huì)根據(jù)隊(duì)列的不同類型抑堡,執(zhí)行不同的函數(shù)
摆出。
_dispatch_lane_concurrent_push
下面我們看下_dispatch_lane_concurrent_push方法,源碼實(shí)現(xiàn)我們看到_dispatch_lane_concurrent_push源碼分兩步實(shí)現(xiàn):
- 1.
_dispatch_continuation_redirect_push
- 2.
_dispatch_lane_push
通過(guò)符號(hào)斷點(diǎn)運(yùn)行首妖,發(fā)現(xiàn)并發(fā)隊(duì)列會(huì)執(zhí)行_dispatch_continuation_redirect_push方法
我們發(fā)現(xiàn)這個(gè)
_dispatch_continuation_redirect_push
也執(zhí)行的dx_push
偎漫。上面我們分析_dispatch_continuation_async就會(huì)調(diào)用dx_push
,此處也調(diào)用有缆,會(huì)形成遞歸嗎象踊?答案肯定是不會(huì)!原因在前面隊(duì)列創(chuàng)建時(shí)可知棚壁,隊(duì)列也是一個(gè)對(duì)象杯矩,有父類,根類袖外。所以此時(shí)調(diào)用dx_push是調(diào)用的父類或者根類方法
史隆。
上面說(shuō)了dx_push會(huì)調(diào)用父類或者根類方法
,上面說(shuō)了dx_push會(huì)調(diào)用dq_push
曼验,上面我們羅列了dq_push會(huì)調(diào)用哪些方法泌射,根類方法在684-705行
粘姜,此時(shí)調(diào)用的方法為_dispatch_root_queue_push
,我們打斷點(diǎn)魄幕,看看是不是會(huì)走這個(gè)方法
我們來(lái)看下_dispatch_root_queue_push源碼纯陨,已將將無(wú)關(guān)緊要的隱藏了通過(guò)上面圖我們知道
并發(fā)隊(duì)列會(huì)調(diào)用_dispatch_root_queue_push方法
相艇,所以我們上面說(shuō)的dx_push調(diào)用會(huì)調(diào)用父類或者根類是對(duì)的
。
我們通過(guò)_dispatch_root_queue_push看到執(zhí)行順序:_dispatch_root_queue_push->_dispatch_root_queue_push_inline->_dispatch_root_queue_poke->_dispatch_root_queue_poke_slow
坛芽,我們主要看下_dispatch_root_queue_poke_slow的源碼
這個(gè)方法我們把不主要的方法給隱藏了,我們說(shuō)下主要方法:
- 1.
通過(guò)do-while循環(huán)創(chuàng)建線程
翼抠,使用pthread_create方法創(chuàng)建(第6223行) - 2.
通過(guò)_dispatch_root_queues_init方法注冊(cè)回調(diào)
(第6233行)
我們看到調(diào)用了dispatch_once_f咙轩,這個(gè)是個(gè)單例
(后續(xù)會(huì)做個(gè)單例的底層分析,這里不做說(shuō)明)阴颖,其中傳入的func是_dispatch_root_queues_init_once
活喊,下面我們?cè)俨榭聪耞dispatch_root_queues_init_once源碼實(shí)現(xiàn),我們看主要的量愧。
上圖我們看到進(jìn)入_dispatch_root_queues_init_once的源碼钾菊,其內(nèi)部不同事務(wù)的調(diào)用句柄是_dispatch_worker_thread2
(第7641行,第7650行偎肃,7666行)煞烫。
通過(guò)上面可以知道,其block回調(diào)執(zhí)行的調(diào)用路徑為:_dispatch_root_queues_init_once -> _dispatch_worker_thread2 -> _dispatch_root_queue_drain -> _dispatch_continuation_pop_inline -> _dispatch_continuation_invoke_inline -> _dispatch_client_callout -> dispatch_call_block_and_release
下面我們通過(guò)打印堆棧信息來(lái)驗(yàn)證一下
通過(guò)打印得出我們的推測(cè)是對(duì)的累颂。
說(shuō)明
這里需要說(shuō)明一點(diǎn)的是滞详,單例的block回調(diào)
和異步函數(shù)的block回調(diào)是不同的
- 1.
單例中
,block回調(diào)中的func是_dispatch_Block_invoke(block)
- 2.而異步函數(shù)中紊馏,
block回調(diào)中的func是dispatch_call_block_and_release
總結(jié)
綜上所述料饥,異步函數(shù)的底層分析如下
- 1.【準(zhǔn)備工作】:首先,將
異步任務(wù)拷貝并封裝
朱监,并設(shè)置回調(diào)函數(shù)func
- 2.【block回調(diào)】:底層
通過(guò)dx_push遞歸
岸啡,會(huì)重定向到根隊(duì)列
,然后通過(guò)pthread_creat創(chuàng)建線程
赌朋,最后通過(guò)dx_invoke執(zhí)行block回調(diào)
(注意dx_push 和 dx_invoke 是成對(duì)的)
同步函數(shù)
進(jìn)入dispatch_async源碼實(shí)現(xiàn)凰狞,其底層實(shí)現(xiàn)是通過(guò)柵欄函數(shù)實(shí)現(xiàn)
的(柵欄函數(shù)后面說(shuō)明)
上面我們可以看到dispatch_sync->_dispatch_sync_f->_dispatch_sync_f_inline順序執(zhí)行,看下_dispatch_sync_f_inline的方法
- 1.
dq->dq_width等于1表示串行隊(duì)列
- 2.
_dispatch_barrier_sync_f為柵欄函數(shù)
沛慢,可以看到同步函數(shù)的底層實(shí)現(xiàn)其實(shí)是同步柵欄函數(shù)
赡若。 - 3._dispatch_sync_f_slow
死鎖
,我們之前驗(yàn)證相互等待的死鎖報(bào)錯(cuò)時(shí)
团甲,就執(zhí)行了這個(gè)方法
逾冬。
我們看下_dispatch_sync_f_slow的具體方法實(shí)現(xiàn),調(diào)用_dispatch_sync_f_slow方法,表明當(dāng)前的隊(duì)列是掛起身腻,阻塞的
dispatch_trace_item_push方法執(zhí)行完成后嘀趟,就會(huì)執(zhí)行__DISPATCH_WAIT_FOR_QUEUE_脐区。下面我們看下DISPATCH_WAIT_FOR_QUEUE
接著執(zhí)行_dq_state_drain_locked_by->_dispatch_lock_is_locked_by_dispatch_wait_prepare:
判斷dq是否為正在等待的主隊(duì)列
,然后給出一個(gè)狀態(tài)state她按,然后將dq的狀態(tài)和當(dāng)前任務(wù)依賴的隊(duì)列進(jìn)行匹配
如果當(dāng)前等待和正在執(zhí)行的是同一個(gè)隊(duì)列(判斷線程的id是否相等)牛隅,如果是同一個(gè)隊(duì)列即判斷為死鎖
同步函數(shù)+并發(fā)隊(duì)列
在_dispatch_sync_invoke_and_complete -> _dispatch_sync_function_invoke_inline源碼中,主要有三個(gè)步驟:
- 1.
將任務(wù)壓入隊(duì)列:_dispatch_thread_frame_push
- 2.
執(zhí)行任務(wù)的block回調(diào):_dispatch_client_callout
- 3.
將任務(wù)出隊(duì):_dispatch_thread_frame_pop
從實(shí)現(xiàn)中可以看出酌泰,是先將任務(wù)push隊(duì)列中
媒佣,然后執(zhí)行block回調(diào)
,再將任務(wù)pop
陵刹,所以任務(wù)是順序執(zhí)行的
默伍。
總結(jié)
同步函數(shù)的底層實(shí)現(xiàn):
- 1.同步函數(shù)的
底層實(shí)現(xiàn)實(shí)際是同步柵欄函數(shù)
- 2.
同步函數(shù)中如果當(dāng)前正在執(zhí)行的隊(duì)列和等待的是同一個(gè)隊(duì)列
,形成相互等待的局面衰琐,則會(huì)造成死鎖
寫到最后
這篇文章主要講了dispatch_async(異步任務(wù))和dispatch_sync(同步任務(wù))底層方法調(diào)用過(guò)程也糊,也解釋也死鎖的底層判斷。下篇我們主要研究下柵欄函數(shù)羡宙,單例显设,信號(hào)量。