iOS中同步函数发生死锁的原因分析

983 阅读2分钟

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

在之前的文章中,我们介绍了函数和队列,也了解了函数的堆栈的调用;今天我们来分析一下同步函数的死锁是怎么发生的?

同步函数死锁

我们从同步函数的调用入口dispatch_sync为切入点来进行分析:

dispatch_sync

  • unlikely意为不大可能发生的,通常认为其后的条件为0;在此处的意思是_dispatch_block_has_private_data(work)函数返回结果通常是false;关于这个dc_flags官方的解释是:如果dc_flags小于0x1000,则该对象是延续。否则,对象有一个私有的布局内存管理规则;
  • 所以我们继续分析的应该是_dispatch_sync_f方法,dq是我们的函数所处的队列,在此处针对work进行了一层_dispatch_Block_invoke的封装;work就是我们同步函数执行的回调,也就是我们具体的任务
  • DC_FLAG_BLOCK是一个宏定义,其值为0x010ul
  • 我们分析过程中的重点应该放在dqwork上;

_dispatch_sync_f

  • dq为我们的函数所处的队列
  • ctxt指向我们的work,也就是具体执行的任务
  • func指的是_dispatch_Block_invoke(work);
  • dc_flags值为0x010ul

_dispatch_sync_f_inline

  • likely意为:经常发生,很可能发生的
  • dq_width == 1,在上一篇文章中我们已经分析过了,dq_width == 1表示当前队列是一个串行队列;
  • _dispatch_barrier_sync_f产生死锁;

_dispatch_barrier_sync_f

继续调用:_dispatch_barrier_sync_f_inline

_dispatch_barrier_sync_f_inline

  • _dispatch_sync_f_slow这个函数我们应该有印象,在上一篇文章中,我们演示死锁时,发生异常时的堆栈信息最终调用的就是_dispatch_sync_f_slow;

但是需要注意的是,_dispatch_sync_f_slow的调用并没有报错,最终报错的是__DISPATCH_WAIT_FOR_QUEUE__:

  • 真机

  • 模拟器

_dispatch_sync_f_slow

  • __DISPATCH_WAIT_FOR_QUEUE__就是最终导致死锁报错的地方;

DISPATCH_WAIT_FOR_QUEUE

我们发现在此函数中出现了与模拟器一摸一样的死锁日志信息:

那么,进一步说明了模拟器的死锁出现在了此处,接下来我们来分析一下死锁的条件;

  • 首先根据日志信息:同步函数调用的队列也就是同步函数所在的队列,已经被当前的线程持有了;
  • _dq_state_drain_locked_by(dq_state, dsc->dsc_waiter):根据_dispatch_sync_f_slow函数实现我们知道dsc->dsc_waiter指向了_dispatch_tid_self()_dispatch_tid_self()就是当前队列线程的线程ID:
#if TARGET_OS_MAC
#define _dispatch_tid_self()		((dispatch_tid)_dispatch_thread_port())
#elif defined(__linux__)
#define _dispatch_tid_self()        ((dispatch_tid)(_dispatch_get_tsd_base()->tid))
#elif defined(_WIN32)
#define _dispatch_tid_self()		((dispatch_tid)(_dispatch_get_tsd_base()->tid << 2))
  • dq_state:当前等待的队列的状态

_dq_state_drain_locked_by

_dispatch_lock_is_locked_by

  • DLOCK_OWNER_MASK值为#define DLOCK_OWNER_MASK ((dispatch_lock)0xfffffffc),是一个很大的值
  • ((lock_value ^ tid) & DLOCK_OWNER_MASK)只要(lock_value ^ tid)不为0,那么结果就一定不为0,那么此处很明显(lock_value ^ tid)结果为0(lock_value ^ tid)结果为0说明lock_valuetid相同;
  • 也就是当前要调用的队列dq目前是等待状态,所以发生了死锁;

总结

如果当前将要调用的队列处于等待状态,那么就会产生死锁; 需要注意的是,串行队列同步函数并不是一定会导致死锁;