iOS GCD死锁底层探究和造成死锁的条件

平常面试中总会遇见GCD死锁的问题,那些情况下会造成死锁呢,先看一下CGD死锁崩溃的核心源码

 __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条件为真时就会进入crash(这里崩溃的原因这两句英文已经解释了),那什么情况下if条件为真呢,点击_dispatch_lock_is_locked_by我们进一步分析

_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
    //lock_value: 当前队列
    //tid: 当前线程
    // equivalent to _dispatch_lock_owner(lock_value) == tid
    return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}

lock_value 当前传进来的队列,
tid 当前所在的线程
DLOCK_OWNER_MASK: 宏 ((dispatch_lock)0xfffffffc)一个很大的数字

分析 ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0
当一个数 &(与上) DLOCK_OWNER_MASK == 0 那这个数一定是0, 所以(lock_value ^ tid) 为0
(lock_value ^(异或) tid) 为0 也就说明 lock_value 和 tid 相等,也就是当前的队列和当前的线程要有关联 并是不是意思上的相等。(可以理解为同一个线程下同一个队列)
((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0 成立时,if条件为真就会促发崩溃 。

从上可以得出一个结论:

在当前线程中同步(sync)的向同一个串行队列添加任务时就会死锁崩溃。

举几个死锁例子来深刻理解上述结论的意思:

以下代码都在viewDidLoad中执行

- (void)viewDidLoad {
    [super viewDidLoad];

    //结论:在当前线程中同步(sync)的向同一个串行队列添加任务时就会死锁崩溃。
    
    //例子1
    dispatch_sync(dispatch_get_main_queue(), ^{
        1 sync(同步)
        2 和 viewDidLoad 都是主线程 
        3 和 viewDidLoad 都是主队列 dispatch_get_main_queue
        4 主队列都是 串行队列
    });
  
    //例子2
    dispatch_queue_t lrQueue = dispatch_queue_create("xx", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(lrQueue, ^{
        当前主线程
        dispatch_sync(lrQueue, ^{
             1 添加的任务为同步 sync
             2 都是同一个串行队列 lrQueue
             3 都在同一个线程中
        });
    });
    
    //例子3
    dispatch_async(lrQueue, ^{
        当前子线程
        dispatch_sync(lrQueue, ^{
            1 添加的任务为同步 sync
            2 都是同一个串行队列 lrQueue
            3 添加的任务都在同一个子线程中, sync是不具备开启线程的能力的
        });
    });
    
   //例子4
    dispatch_sync(lrQueue, ^{
        //当前主线程
        dispatch_sync(dispatch_get_main_queue(), ^{
             1 添加的任务为同步 sync
             2 这里是 dispatch_get_main_queue 主队列,和 viewDidLoad 的队列相等    
             3 都在同一个主线程中
        });
    });

  修改一下例子4 
    dispatch_async(lrQueue, ^{
        当前子线程
        dispatch_sync(dispatch_get_main_queue(), ^{
            1 添加的任务为同步 sync
            2 这里是 dispatch_get_main_queue 主队列,和 viewDidLoad的队列相等
            3 viewDidLoad 是在主线程, 但是添加的任务是在子线程中 (条件不满足)
            4 sync是不会开启线程的 async才会开启线程

            所以条件不满足不是同一个线程 不会崩溃
        });
    });
}

综上只满足 在同一个线程中,同步的向同一个串行队列中添加任务就会崩溃。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容