首先低零,看看如下代碼的輸出是什么么库?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"Hello");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"World");
});
}
首先答案是會發(fā)生死鎖购笆,我們看看官方文檔關(guān)于dispatch_sync的解釋:
Submits a block to a dispatch queue like dispatch_async(), however
dispatch_sync() will not return until the block has finished.
Calls to dispatch_sync() targeting the current queue will result
in dead-lock. Use of dispatch_sync() is also subject to the same
multi-party dead-lock problems that may result from the use of a mutex.
Use of dispatch_async() is preferred.
Unlike dispatch_async(), no retain is performed on the target queue. Because
calls to this function are synchronous, the dispatch_sync() "borrows" the
reference of the caller.
As an optimization, dispatch_sync() invokes the block on the current
thread when possible.
如果dispatch_sync()的目標(biāo)queue為當(dāng)前queue剃浇,會發(fā)生死鎖(并行queue并不會)拢切。使用dispatch_sync()會遇到跟我們在pthread中使用mutex鎖一樣的死鎖問題蒂萎。
話是這么說,我們看看究竟是怎么做的淮椰?先放碼:
source/queue.c
void
dispatch_sync(dispatch_queue_t dq, void (^work)(void))
{
struct Block_basic *bb = (void *)work;
dispatch_sync_f(dq, work, (dispatch_function_t)bb->Block_invoke);
}
DISPATCH_NOINLINE
void
dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
typeof(dq->dq_running) prev_cnt;
dispatch_queue_t old_dq;
if (dq->dq_width == 1) {
return dispatch_barrier_sync_f(dq, ctxt, func);
}
// 1) ensure that this thread hasn't enqueued anything ahead of this call
// 2) the queue is not suspended
if (slowpath(dq->dq_items_tail) || slowpath(DISPATCH_OBJECT_SUSPENDED(dq))) {
_dispatch_sync_f_slow(dq);
} else {
prev_cnt = dispatch_atomic_add(&dq->dq_running, 2) - 2;
if (slowpath(prev_cnt & 1)) {
if (dispatch_atomic_sub(&dq->dq_running, 2) == 0) {
_dispatch_wakeup(dq);
}
_dispatch_sync_f_slow(dq);
}
}
old_dq = _dispatch_thread_getspecific(dispatch_queue_key);
_dispatch_thread_setspecific(dispatch_queue_key, dq);
func(ctxt);
_dispatch_workitem_inc();
_dispatch_thread_setspecific(dispatch_queue_key, old_dq);
if (slowpath(dispatch_atomic_sub(&dq->dq_running, 2) == 0)) {
_dispatch_wakeup(dq);
}
}
Step1
. 可以看到dispatch_sync將我們block函數(shù)指針進(jìn)行了一些轉(zhuǎn)換后五慈,直接傳給了dispatch_sync_f()去處理纳寂。
Step2
. dispatch_sync_f首先檢查傳入的隊列寬度(dq_width),由于我們傳入的main queue
為串行隊列泻拦,隊列寬度為1毙芜,所有接下來會調(diào)用dispatch_barrier_sync_f,傳入3個參數(shù)聪轿,dispatch_sync中的目標(biāo)queue爷肝、上下文信息和由我們block函數(shù)指針轉(zhuǎn)化過后的func結(jié)構(gòu)體。
接下來我們看看dispatch_barrier_sync_f的實現(xiàn)
source/queue.c
void
dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
dispatch_queue_t old_dq = _dispatch_thread_getspecific(dispatch_queue_key);
// 1) ensure that this thread hasn't enqueued anything ahead of this call
// 2) the queue is not suspended
// 3) the queue is not weird
if (slowpath(dq->dq_items_tail)
|| slowpath(DISPATCH_OBJECT_SUSPENDED(dq))
|| slowpath(!_dispatch_queue_trylock(dq))) {
return _dispatch_barrier_sync_f_slow(dq, ctxt, func);
}
_dispatch_thread_setspecific(dispatch_queue_key, dq);
func(ctxt);
_dispatch_workitem_inc();
_dispatch_thread_setspecific(dispatch_queue_key, old_dq);
_dispatch_queue_unlock(dq);
}
Step3
. disptach_barrier_sync_f首先做了做了3個判斷:
- 隊列存在尾部節(jié)點狀態(tài)(判斷當(dāng)前是不是處于隊列尾部)
- 隊列不為暫停狀態(tài)
- 使用_dispatch_queue_trylock檢查隊列能被正常加鎖陆错。
滿足所有條件則不執(zhí)行if語句內(nèi)的內(nèi)容灯抛,執(zhí)行下面代碼,簡單解釋為:
- 使用mutex鎖音瓷,獲取到當(dāng)前進(jìn)程資源鎖对嚼。
- 直接執(zhí)行我們block函數(shù)指針的具體內(nèi)容。
- 然后釋放鎖绳慎,整個調(diào)用結(jié)束纵竖。
然后在我們例子中,很顯然當(dāng)前隊列中還有其他viewController的任務(wù)杏愤,我們的流程跑到_dispatch_barrier_aync_f_slow()函數(shù)體中靡砌。
刨根問底,讓我們看看這個函數(shù)珊楼。
source/queue.c
static void
_dispatch_barrier_sync_f_slow(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
// It's preferred to execute synchronous blocks on the current thread
// due to thread-local side effects, garbage collection, etc. However,
// blocks submitted to the main thread MUST be run on the main thread
struct dispatch_barrier_sync_slow2_s dbss2 = {
.dbss2_dq = dq,
#if DISPATCH_COCOA_COMPAT
.dbss2_func = func,
.dbss2_ctxt = ctxt,
#endif
.dbss2_sema = _dispatch_get_thread_semaphore(),
};
struct dispatch_barrier_sync_slow_s {
DISPATCH_CONTINUATION_HEADER(dispatch_barrier_sync_slow_s);
} dbss = {
.do_vtable = (void *)DISPATCH_OBJ_BARRIER_BIT,
.dc_func = _dispatch_barrier_sync_f_slow_invoke,
.dc_ctxt = &dbss2,
};
//---------------重點是這里---------------
_dispatch_queue_push(dq, (void *)&dbss);
dispatch_semaphore_wait(dbss2.dbss2_sema, DISPATCH_TIME_FOREVER);
_dispatch_put_thread_semaphore(dbss2.dbss2_sema);
#if DISPATCH_COCOA_COMPAT
// Main queue bound to main thread
if (dbss2.dbss2_func == NULL) {
return;
}
#endif
dispatch_queue_t old_dq = _dispatch_thread_getspecific(dispatch_queue_key);
_dispatch_thread_setspecific(dispatch_queue_key, dq);
func(ctxt);
_dispatch_workitem_inc();
_dispatch_thread_setspecific(dispatch_queue_key, old_dq);
dispatch_resume(dq);
}
Step4
. 既然我們上面已經(jīng)判斷了通殃,main queue中還有其他任務(wù),現(xiàn)在不能直接執(zhí)行這個block厕宗,跳入到_dispatch_barrier_sync_f_slow函數(shù)體画舌,那它怎么處理我們加入的block呢?
在_dispatch_barrier_sync_f_slow中已慢,使用_dispatch_queue_push將我們的block壓入main queue的FIFO隊列中曲聂,然后等待
信號量,ready后被喚醒佑惠。
然后dispatch_semaphore_wait返回_dispatch_semaphore_wait_slow(dsema, timeout)函數(shù)朋腋,持續(xù)輪訓(xùn)并等待
,直到條件滿足膜楷。
所以在此過程中乍丈,我們最初調(diào)用的dispatch_sync函數(shù)一直得不到返回,main queue被阻塞把将,而我們的block又需要等待main queue來執(zhí)行它。死鎖愉快的產(chǎn)生了忆矛。
最后:
我們繪制上張圖來輕松的描述一下這個問題: