这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战markdown
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录个人不断探索之旅,但愿能有帮助到各位读者朋友。
复制代码
咱们从GCD函数和队列
的内容中最后的经典案例中关于死锁的案例开始,从死锁的发生开始,看看其产生的本质缘由是为何。话很少说这就开始。数据结构
引用咱们在GCD函数和队列
一文中和死锁相关的内容:多线程
当咱们但愿任务以特定的顺序执行时,串行队列颇有用。串行队列一次只执行一个任务,而且老是从队列的头部拉去任务。咱们能够
使用串行队列而不是锁来保护共享资源或可变数据结构
。与锁不一样,串行队列确保任务以能够预测的顺序执行。并且 只要咱们将任务异步提交到串行队列,队列就永远不会死锁
。并发
有两种方法能够将任务添加到队列中:异步或同步。若是可能,使用
dispatch_async
和dispatch_async_f
函数的异步执行优先于同步替代方案。当您将块对象或函数添加到队列时,没法知道该代码什么时候执行。所以,异步添加块或函数可以让您安排代码的执行并继续从调用线程执行其余工做。若是您从应用程序的主线程调度任务,这尤为重要——也许是为了响应某些用户事件。异步尽管您应该尽量以异步方式添加任务,但有时您仍可能须要同步添加任务以防止竞争条件或其余同步错误。在这些状况下,您可使用
dispatch_sync
和dispatch_sync_f
函数将任务添加到队列中。这些函数会阻塞当前的执行线程,直到指定的任务完成执行。async重要提示: 您永远不该从在您计划传递给函数的同一队列中执行的任务调用
dispatch_sync
或dispatch_sync_f
函数。这对于保证死锁的串行队列尤为重要,但对于并发队列也应避免
。函数
以上两部份内容,是在阐述串行队列的概念解释和将任务添加到队列中的两种方式的规范内容。总的来讲,是帮助咱们在正确使用串行队列,以及在将任务添加到队列中时,避免死锁
的发生。post
下面,咱们从案例中的死锁开始。学习
正如上面的重要提示中锁阐述的同样,咱们永远不该该将函数添加到队列中执行任务时使用同步的方式。这对于保证死锁的串行队列尤为重要,但对于并发队列也应避免。ui
的确,这是避免死锁的重要思路,可是,仍是难以免,在实际开发中,咱们使用了下面的代码:
第180行,咱们的程序发生来死锁,从堆栈的信息中能够看到是 :
libdispatch.dylib_dispatch_sync_f_slow:
-> libdispatch.dylib__DISPATCH_WAIT_FOR_QUEUE__
:
在180行,打上断点。在GCD函数和队列
篇章中,咱们知道dispatch_syn函数的执行流程以下:
dispatch_sync -> _dispatch_sync_f -> _dispatch_sync_f_inline
在_dispatch_sync_f_inline中,有五条分支,咱们分别下五个符号断点:
static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt, dispatch_function_t func, uintptr_t dc_flags)
{
if (likely(dq->dq_width == 1)) {
return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
}
if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
}
dispatch_lane_t dl = upcast(dq)._dl;
// 全局并发队列和绑定到非分派线程的队列
// 老是落在慢的状况下 DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {
return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);
}
if (unlikely(dq->do_targetq->do_targetq)) {
return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);
}
_dispatch_introspection_sync_begin(dl);
_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));
}
复制代码
先来到了 _dispatch_barrier_sync_f
分支
static void
_dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func, uintptr_t dc_flags)
{
_dispatch_barrier_sync_f_inline(dq, ctxt, func, dc_flags);
}
复制代码
再下 _dispatch_barrier_sync_f_inline
符号断点, 很遗憾没有断住,直接来到了 _dispatch_sync_f_slow
:
咱们来到 _dispatch_barrier_sync_f_inline
内部的实现:
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt, dispatch_function_t func, uintptr_t dc_flags)
{
dispatch_tid tid = _dispatch_tid_self();
if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
}
dispatch_lane_t dl = upcast(dq)._dl;
// 更正确的作法是合并线程的qos
// 刚刚得到了进入队列状态的barrier锁。
//
// 然而,这对于快速路径来讲太昂贵了,因此跳过它。
// 选择的权衡是,若是队列在较低优先级的线程上
// 与此快速路径,此线程可能收到无用的覆盖。
//
// 全局并发队列和绑定到非分派线程的队列
// 老是落在慢的状况下 DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {
return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,
DC_FLAG_BARRIER | dc_flags);
}
if (unlikely(dl->do_targetq->do_targetq)) {
return _dispatch_sync_recurse(dl, ctxt, func,
DC_FLAG_BARRIER | dc_flags);
}
_dispatch_introspection_sync_begin(dl);
_dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));
}
复制代码
(_dispatch_sync_f_inline 和 _dispatch_barrier_sync_f_inline 内部实现有点类似)
其内部的第二个分支即是调用 _dispatch_sync_f_slow
DISPATCH_NOINLINE
static void
_dispatch_sync_f_slow(dispatch_queue_class_t top_dqu, void *ctxt, dispatch_function_t func, uintptr_t top_dc_flags, dispatch_queue_class_t dqu, uintptr_t dc_flags)
{
dispatch_queue_t top_dq = top_dqu._dq;
dispatch_queue_t dq = dqu._dq;
if (unlikely(!dq->do_targetq)) {
return _dispatch_sync_function_invoke(dq, ctxt, func);
}
pthread_priority_t pp = _dispatch_get_priority();
struct dispatch_sync_context_s dsc = {
.dc_flags = DC_FLAG_SYNC_WAITER | dc_flags,
.dc_func = _dispatch_async_and_wait_invoke,
.dc_ctxt = &dsc,
.dc_other = top_dq,
.dc_priority = pp | _PTHREAD_PRIORITY_ENFORCE_FLAG,
.dc_voucher = _voucher_get(),
.dsc_func = func,
.dsc_ctxt = ctxt,
.dsc_waiter = _dispatch_tid_self(),
};
_dispatch_trace_item_push(top_dq, &dsc);
__DISPATCH_WAIT_FOR_QUEUE__(&dsc, dq);
if (dsc.dsc_func == NULL) {
// dsc_func being cleared means that the block ran on another thread ie.
// case (2) as listed in _dispatch_async_and_wait_f_slow.
dispatch_queue_t stop_dq = dsc.dc_other;
return _dispatch_sync_complete_recurse(top_dq, stop_dq, top_dc_flags);
}
_dispatch_introspection_sync_begin(top_dq);
_dispatch_trace_item_pop(top_dq, &dsc);
_dispatch_sync_invoke_and_complete_recurse(top_dq, ctxt, func,top_dc_flags
DISPATCH_TRACE_ARG(&dsc));
}
复制代码
看到其实现内容,依然会以为和上面两个(_dispatch_sync_f_inline 和 _dispatch_barrier_sync_f_inline) 有点类似。 再次根据分支下符号断点,就断不住了,直接会崩溃,根据堆栈,咱们会来到: __DISPATCH_WAIT_FOR_QUEUE__(&dsc, dq)
的执行。
__DISPATCH_WAIT_FOR_QUEUE__
DISPATCH_NOINLINE
static void
__DISPATCH_WAIT_FOR_QUEUE__(dispatch_sync_context_t dsc, dispatch_queue_t dq)
{
uint64_t dq_state = _dispatch_wait_prepare(dq);
if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) {
DISPATCH_CLIENT_CRASH((uintptr_t)dq_state,
"dispatch_sync called on queue "
"already owned by current thread");
}
...
}
复制代码
程序崩溃在这里,那么,咱们就须要重点分析下这里的 if 判断条件是什么?符合了怎样的条件,以致于程序崩溃的发生。
函数对比的两个内容:
// 一、当前的队列线程的线程ID
#define _dispatch_tid_self() ((dispatch_tid)_dispatch_thread_port())
#define _dispatch_thread_port() pthread_mach_thread_np(_dispatch_thread_self())
#define _dispatch_thread_self() ((uintptr_t)pthread_self())
// 二、队列的状态
_dispatch_wait_prepare(dq);
复制代码
_dq_state_drain_locked_by
ISPATCH_ALWAYS_INLINE
static inline bool
_dq_state_drain_locked_by(uint64_t dq_state, dispatch_tid tid)
{
return _dispatch_lock_is_locked_by((dispatch_lock)dq_state, tid);
}
#define DLOCK_OWNER_MASK ((dispatch_lock)0xfffffffc)
DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
// equivalent to _dispatch_lock_owner(lock_value) == tid
// DLOCK_OWNER_MASK 是一个很大的数 ((dispatch_lock)0xfffffffc)
// 前面的结果只要不为0 与上 DLOCK_OWNER_MASK 也不为0
// 若是前面的结果与上DLOCK_OWNER_MASK结果为0 那前面的结果 必然为0
// 最终, lock_value 和 tid 相同 才会 为 0
return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
复制代码
最后, 原本这个锁住要等待的线程的状态和咱们的线程ID相同。也就是咱们的线程原本应该在等待状态,然而这个时候,又调用了线程的队列来添加任务,告诉系统要调起此线程,结果在咱们的系统中此线程又是等待的状态。因此,这次添加任务是没法实现的。
在这里,又要调起线程,而后线程又是等待状态,此时就是一个矛盾,没法继续执行下去,因此就发生了死锁
。