iOS底层 - 关于死锁,你了解多少?

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战markdown

写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录个人不断探索之旅,但愿能有帮助到各位读者朋友。
复制代码

目录以下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)
  13. iOS 底层原理探索 之 应用程序加载原理dyld (下)
  14. iOS 底层原理探索 之 类的加载
  15. iOS 底层原理探索 之 分类的加载
  16. iOS 底层原理探索 之 关联对象
  17. iOS底层原理探索 之 魔法师KVC
  18. iOS底层原理探索 之 KVO原理|8月更文挑战
  19. iOS底层原理探索 之 重写KVO|8月更文挑战
  20. iOS底层原理探索 之 多线程原理|8月更文挑战
  21. iOS底层原理探索 之 GCD函数和队列
  22. iOS底层原理探索 之 GCD原理(上)

以上内容的总结专栏


细枝末节整理


前言

咱们从GCD函数和队列的内容中最后的经典案例中关于死锁的案例开始,从死锁的发生开始,看看其产生的本质缘由是为何。话很少说这就开始。数据结构

引用咱们在GCD函数和队列一文中和死锁相关的内容:多线程

  • 建立串行调度队列的解释:

当咱们但愿任务以特定的顺序执行时,串行队列颇有用。串行队列一次只执行一个任务,而且老是从队列的头部拉去任务。咱们能够使用串行队列而不是锁来保护共享资源或可变数据结构。与锁不一样,串行队列确保任务以能够预测的顺序执行。并且 只要咱们将任务异步提交到串行队列,队列就永远不会死锁并发

  • 在将单个任务添加到队列中的解释:

有两种方法能够将任务添加到队列中:异步或同步。若是可能,使用dispatch_asyncdispatch_async_f函数的异步执行优先于同步替代方案。当您将块对象或函数添加到队列时,没法知道该代码什么时候执行。所以,异步添加块或函数可以让您安排代码的执行并继续从调用线程执行其余工做。若是您从应用程序的主线程调度任务,这尤为重要——也许是为了响应某些用户事件。异步

尽管您应该尽量以异步方式添加任务,但有时您仍可能须要同步添加任务以防止竞争条件或其余同步错误。在这些状况下,您可使用dispatch_syncdispatch_sync_f函数将任务添加到队列中。这些函数会阻塞当前的执行线程,直到指定的任务完成执行。async

重要提示: 您永远不该从在您计划传递给函数的同一队列中执行的任务调用dispatch_syncdispatch_sync_f函数。这对于保证死锁的串行队列尤为重要,但对于并发队列也应避免函数

以上两部份内容,是在阐述串行队列的概念解释和将任务添加到队列中的两种方式的规范内容。总的来讲,是帮助咱们在正确使用串行队列,以及在将任务添加到队列中时,避免死锁的发生。post

下面,咱们从案例中的死锁开始。学习

死锁的发生

正如上面的重要提示中锁阐述的同样,咱们永远不该该将函数添加到队列中执行任务时使用同步的方式。这对于保证死锁的串行队列尤为重要,但对于并发队列也应避免。ui

的确,这是避免死锁的重要思路,可是,仍是难以免,在实际开发中,咱们使用了下面的代码:

串行队列,同步函数中同步函数

image.png

第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中,有五条分支,咱们分别下五个符号断点:

_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 分支

image.png

_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 :

image.png

咱们来到 _dispatch_barrier_sync_f_inline 内部的实现:

_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_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) 的执行。

image.png image.png

最终奔溃的点

__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相同。也就是咱们的线程原本应该在等待状态,然而这个时候,又调用了线程的队列来添加任务,告诉系统要调起此线程,结果在咱们的系统中此线程又是等待的状态。因此,这次添加任务是没法实现的。

在这里,又要调起线程,而后线程又是等待状态,此时就是一个矛盾,没法继续执行下去,因此就发生了死锁

相关文章
相关标签/搜索