dispatch_sync死鎖問題

首先,看看以下代碼的輸出是什麼?swift

- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"Hello"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"World"); }); }

首先答案是會發生死鎖,咱們看看官方文檔關於dispatch_sync的解釋:async

Submits a block to a dispatch queue like dispatch_async(), however
dispatch_sync() will not return until the block has finished.ide

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.ui

As an optimization, dispatch_sync() invokes the block on the current
thread when possible.this

若是dispatch_sync()的目標queue爲當前queue,會發生死鎖(並行queue並不會)。使用dispatch_sync()會遇到跟咱們在pthread中使用mutex鎖同樣的死鎖問題。atom

話是這麼說,咱們看看到底是怎麼作的?先放碼:spa

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函數指針進行了一些轉換後,直接傳給了dispatch_sync_f()去處理。code

Step2. dispatch_sync_f首先檢查傳入的隊列寬度(dq_width),因爲咱們傳入的main queue爲串行隊列,隊列寬度爲1,全部接下來會調用dispatch_barrier_sync_f,傳入3個參數,dispatch_sync中的目標queue、上下文信息和由咱們block函數指針轉化事後的func結構體。

接下來咱們看看dispatch_barrier_sync_f的實現

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個判斷:

  • 隊列存在尾部節點狀態(判斷當前是否是處於隊列尾部)
  • 隊列不爲暫停狀態
  • 使用_dispatch_queue_trylock檢查隊列能被正常加鎖。

知足全部條件則不執行if語句內的內容,執行下面代碼,簡單解釋爲:

  • 使用mutex鎖,獲取到當前進程資源鎖。
  • 直接執行咱們block函數指針的具體內容。
  • 而後釋放鎖,整個調用結束。

而後在咱們例子中,很顯然當前隊列中還有其餘viewController的任務,咱們的流程跑到_dispatch_barrier_aync_f_slow()函數體中。

刨根問底,讓咱們看看這個函數。

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. 既然咱們上面已經判斷了,main queue中還有其餘任務,如今不能直接執行這個block,跳入到_dispatch_barrier_sync_f_slow函數體,那它怎麼處理咱們加入的block呢?

在_dispatch_barrier_sync_f_slow中,使用_dispatch_queue_push將咱們的block壓入main queue的FIFO隊列中,而後等待信號量,ready後被喚醒。

而後dispatch_semaphore_wait返回_dispatch_semaphore_wait_slow(dsema, timeout)函數,持續輪訓並等待,直到條件知足。

因此在此過程當中,咱們最初調用的dispatch_sync函數一直得不到返回,main queue被阻塞,而咱們的block又須要等待main queue來執行它。死鎖愉快的產生了。

最後:

咱們繪製上張圖來輕鬆的描述一下這個問題:


 
轉http://www.jianshu.com/p/44369c02b62a
相關文章
相關標籤/搜索