iOS底层探索-GCD原理

349 阅读5分钟

进行GCD底层原理探索前,我们需要到Apple官网的 Source Browser 下载GCD源码

1、main_queue

主队列通过dispatch_get_main_queue()获取,那么我们从两条路径探索一下:

  • 第一条路径是源码搜索 dispatch_get_main_queue(

    DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW
    dispatch_queue_main_t
    dispatch_get_main_queue(void)
    {
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
    }
    

    通过查找 DISPATCH_GLOBAL_OBJECT 会发现这是一个宏定义,而且在多处有同名定义,想要继续探索是调用的哪个宏定义可以借助堆栈,但是更值得探索的是 dispatch_queue_main_t_dispatch_main_q 这两个参数

  • 第二条路径是直接打印一下看看会输出什么 image.png

    • 主线程名称为 com.apple.main-thread,那么我们就搜一下这个名字是在什么位置被赋值的
      // init.c文件
      
      // 6618342 Contact the team that owns the Instrument DTrace probe before
      //         renaming this symbol
      struct dispatch_queue_static_s _dispatch_main_q = {
      DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
      #if !DISPATCH_USE_RESOLVERS
          .do_targetq = _dispatch_get_default_queue(true),
      #endif
          .dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
      DISPATCH_QUEUE_ROLE_BASE_ANON,
          .dq_label = "com.apple.main-thread",
          // DQF_WIDTH(1) 与 .dq_serialnum = 1 为重要内容
          .dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
          .dq_serialnum = 1,
      };
      
      • _dispatch_main_q正巧是主队列源码2个参数之一
      • DQF_WIDTH(1)标识了这个队列是串行队列
      • dq_serialnum通过这个内容的值区分队列类型
        // dq_serialnum 是用这个方式赋值的
        dq->dq_serialnum =
        os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
        
        还需要查看这个 _dispatch_queue_serial_numbers 参数
        // skip zero
        // 1 - main_q
        // 2 - mgr_q
        // 3 - mgr_root_q
        // 4,5,6,7,8,9,10,11,12,13,14,15 - global queues
        // 17 - workloop_fallback_q
        // we use 'xadd' on Intel, so the initial value == next assigned
        #define DISPATCH_QUEUE_SERIAL_NUMBER_INIT 17
        extern unsigned long volatile _dispatch_queue_serial_numbers;
        
        这下看注释我们就知道了,通过 _dispatch_queue_serial_numbers 的值可以区分是 主队列系统的mgr_q 还是 global queues,因此也可以说是 通过 dq_serialnum 来区分的队列类型

2、dispatch_queue_create()

我们来看一下创建队列的方法底层是怎么实现的

dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
    return _dispatch_lane_create_with_target(label, attr,
            DISPATCH_TARGET_QUEUE_DEFAULT, true);
}

dispatch_lane_create_with_target

DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
        dispatch_queue_t tq, bool legacy)
{
    //将用户设置的队列类型传入并封装为 dispatch_queue_attr_info_t 类型
    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

    //
    // Step 1: Normalize arguments (qos, overcommit, tq)
    //

    ……

    dispatch_lane_t dq = _dispatch_object_alloc(vtable,
            sizeof(struct dispatch_lane_s));
    // 创建队列,用到了上边封装成 dispatch_queue_attr_info_t 类型时里边设置的 dqai_concurrent 参数
    _dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
            DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
            (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));

    //用户设置的队列名称
    dq->dq_label = label;
    dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
            dqai.dqai_relpri);
    if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
        dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
    }
    if (!dqai.dqai_inactive) {
        _dispatch_queue_priority_inherit_from_target(dq, tq);
        _dispatch_lane_inherit_wlh_from_target(dq, tq);
    }
    _dispatch_retain(tq);
    dq->do_targetq = tq;
    _dispatch_object_debug(dq, "%s", __func__);
    return _dispatch_trace_queue_create(dq)._dq;
}
_dispatch_queue_attr_to_info

将用户设置的串行、并发队列参数传入,并以此设置 dqai_concurrent 标识为true或false作为标记

dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
    dispatch_queue_attr_info_t dqai = { };

    if (!dqa) return dqai;

#if DISPATCH_VARIANT_STATIC
    // 如果传过来的是concurrent则把这个参数设置为true
    if (dqa == &_dispatch_queue_attr_concurrent) {
    dqai.dqai_concurrent = true;
    return dqai;
}
#endif

......

    return dqai;
}
_dispatch_queue_init
// Note to later developers: ensure that any initialization changes are
// made for statically allocated queues (i.e. _dispatch_main_q).
static inline dispatch_queue_class_t
_dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf,
        uint16_t width, uint64_t initial_state_bits)
{
    uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);
    dispatch_queue_t dq = dqu._dq;

    dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |
            DISPATCH_QUEUE_INACTIVE)) == 0);

    if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {
        dq_state |= DISPATCH_QUEUE_INACTIVE + DISPATCH_QUEUE_NEEDS_ACTIVATION;
        dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume
        if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {
            dq->do_ref_cnt++; // released when DSF_DELETED is set
        }
    }

    dq_state |= (initial_state_bits & DISPATCH_QUEUE_ROLE_MASK);
    dq->do_next = DISPATCH_OBJECT_LISTLESS;
    // 队列宽度,为1时就是串行队列
    dqf |= DQF_WIDTH(width);
    os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
    dq->dq_state = dq_state;
    // 队列类型
    dq->dq_serialnum =
            os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
    return dqu;
}
  • 重点关注 dqf |= DQF_WIDTH(width) 这行代码,因为它设置了队列宽度

3、死锁原理

  • 当前线程(线程执行的任务是从同一队列中取出的)同步的向串行队列中添加任务,就会死锁

3.1、__DISPATCH_WAIT_FOR_QUEUE__

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);
    //unlikely:小概率执行
    //返回内容为true时触发死锁
    if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) {
        //抛出CRASH报错
        DISPATCH_CLIENT_CRASH((uintptr_t)dq_state,
                "dispatch_sync called on queue "
                "already owned by current thread");
    }
    ......
}

3.2、_dq_state_drain_locked_by --> _dispatch_lock_is_locked_by

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
    // lock_value:当前队列
    // tid:当前线程id
    return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
  • DLOCK_OWNER_MASK 是一个很大的数,(lock_value ^ tid)这个式子为 0 时才使 return 内容肯定为 true,所以可知为 lock_value == tid

示例

1.    - (void)viewDidLoad {
2.        [super viewDidLoad];

          // 串行队列
3.        dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL);
          // 与第6行都是向 queue 中添加任务后再取到同一线程调度
4.        dispatch_async(queue, ^{
5.            NSLog(@"%@",[NSThread currentThread]);
              // 同步添加
6.            dispatch_sync(queue, ^{
7.                NSLog(@"此处死锁");
8.            });
9.        });
10.   }
  • 第4行是sync或async无关紧要,是sync就不开辟线程,死锁在主线程;是async就开辟一条新线程,死锁在这条新线程

死锁3个条件小结

  1. 串行队列:通道够窄,后边的任务得等着
  2. 同步添加:任务只能等这条线程,不能跑去别的线程
  3. 相同线程任务来源的队列得一致,因为队列会找一条空闲的线程执行任务,如果任务来自不同队列,队列1的任务都还没执行完变空闲,队列2调度的线程怎么可能和队列1的相同,线程都不同,还谈什么死锁?

4、同步、异步函数

4.1、同步函数

//sync
DISPATCH_NOINLINE
void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
    uintptr_t dc_flags = DC_FLAG_BLOCK;
    // unlikely:小概率执行
    if (unlikely(_dispatch_block_has_private_data(work))) {
        return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    }
    // _dispatch_Block_invoke(work)执行block
    _dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}
#endif // __BLOCKS__

//_dispatch_Block_invoke将任务封装为 Block_layout 结构体
#ifdef __BLOCKS__
#define _dispatch_Block_invoke(bb) \
((dispatch_function_t)((struct Block_layout *)bb)->invoke)

4.2、异步函数

//async
#ifdef __BLOCKS__
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME;
    dispatch_qos_t qos;

    // 任务block封装为qos,同时在里边进行了copy保存
    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    //
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
#endif
_dispatch_continuation_init
DISPATCH_ALWAYS_INLINE
static inline dispatch_qos_t
_dispatch_continuation_init(dispatch_continuation_t dc,
        dispatch_queue_class_t dqu, dispatch_block_t work,
        dispatch_block_flags_t flags, uintptr_t dc_flags)
{
    // copy了一份任务block
    void *ctxt = _dispatch_Block_copy(work);

    dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        dc->dc_flags = dc_flags;
        //将copy的任务进行赋值存储
        dc->dc_ctxt = ctxt;
        // will initialize all fields but requires dc_flags & dc_ctxt to be set
        return _dispatch_continuation_init_slow(dc, dqu, flags);
    }

    dispatch_function_t func = _dispatch_Block_invoke(work);
    if (dc_flags & DC_FLAG_CONSUME) {
        func = _dispatch_call_block_and_release;
    }
    return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}
_dispatch_continuation_async
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
    #if DISPATCH_INTROSPECTION
    if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
        _dispatch_trace_item_push(dqu, dc);
    }
#else
    (void)dc_flags;
#endif
    // 这里使用了qos
    return dx_push(dqu._dq, dc, qos);
}
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)

image.png

  • 根据队列串行、并发、全局等类型,.dq_push 会被赋不同的值
  • 以全局队列为例,方法顺序:_dispatch_root_queue_push --> _dispatch_root_queue_poke --> _dispatch_root_queue_poke_slow image.png
    • 操作了线程相关内容
    • 异步底层调用使用大量宏与复杂逻辑,我们主要关注与同步函数的不同

小结

  • _dispatch_client_callout:最后通过这个方法执行任务block
  • dispatch_sync没有保存任务、没有线程相关信息、直接执行
  • dispatch_async对任务block做了保存,操作了线程,在适当位置执行