GCD源码吐血分析(1)——GCD Queue

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013378438/article/details/81031938

看了快半个月的GCD源码,只能说太变态了。
先来吐槽一下:一个函数,调用栈都是十几层…… 为了效率,代码使用了纯C语言,但是为了模拟面向对象中的继承,虚函数等,定义了一层层的宏定义,看一个struct的定义要绕过来绕过去…… 网上的资料极少,有的那几篇,还都是用旧版本的GCD在说事儿,而新版的GCD源码复杂度及晦涩度,在原有的基础上,又升级了一个档次……

说实话,到现在也不敢说是看懂了或是看对了,所以这篇博客只是个人的一个不负责任的总结而已。天哪,让我从GCD的泥潭中出来吧……

基本的数据结构

GCD的类都是struct定义的。但并不像runtime一样,直接使用:来继承定义。而是将所有的父类的数据成员,都平铺重复的写在一个个的struct中,这样做的好处应该是为了提高效率,避免引入继承机制带来的代码执行上的延迟?但是,为了减少代码量和易读性,苹果还是很“贴心”的做了许多宏定义,在阅读源码时,你不得不将这些宏,替换为它真实的定义。

GCD中的数据结构是如下组织的:

这里写图片描述

GCD中类都继承自统一的基类dispatch_object_t, 再下一层,则是表示系统对象的基类_os_object_s

root 基类dispatch_object_t的定义如下:

typedef union {
    struct _os_object_s *_os_obj;
    struct dispatch_object_s *_do;
    struct dispatch_continuation_s *_dc;
    struct dispatch_queue_s *_dq;
    struct dispatch_queue_attr_s *_dqa;
    struct dispatch_group_s *_dg;
    struct dispatch_source_s *_ds;
    struct dispatch_mach_s *_dm;
    struct dispatch_mach_msg_s *_dmsg;
    struct dispatch_source_attr_s *_dsa;
    struct dispatch_semaphore_s *_dsema;
    struct dispatch_data_s *_ddata;
    struct dispatch_io_s *_dchannel;
    struct dispatch_operation_s *_doperation;
    struct dispatch_disk_s *_ddisk;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;

可见它是一个联合,可以表示union中任意的类型。起到的作用和基类指针相似,所有的子类型都可以用dispatch_object_t统一表示。

dispatch_queue_s : GCD中的queue。

dispatch_continuation_s : 我们向queue提交的任务,无论block还是function形式,最终都会被封装为dispatch_continuation_s。

注意,这只是逻辑上的继承关系,在实际的源码中,是看不到继承的符号的。而是通过宏的方式,将父类中的属性,在子类中再写一次。如继承了_os_object_sdispatch_object_s的定义:

struct dispatch_object_s {
    _DISPATCH_OBJECT_HEADER(object);
};

#define _DISPATCH_OBJECT_HEADER(x) \
    struct _os_object_s _as_os_obj[0]; \
    OS_OBJECT_STRUCT_HEADER(dispatch_##x); \  // 这个宏,可以理解为dispatch_object_s继承自_os_object_s
    struct dispatch_##x##_s *volatile do_next; \
    struct dispatch_queue_s *do_targetq; \
    void *do_ctxt; \
    void *do_finalizer

简单的理解了GCD中基本类的关系,我们就来看一下各个类中的定义。

_os_object_s

typedef struct _os_object_s {
    _OS_OBJECT_HEADER(
    const _os_object_vtable_s *os_obj_isa,
    os_obj_ref_cnt,
    os_obj_xref_cnt);
} _os_object_s;

#define _OS_OBJECT_HEADER(isa, ref_cnt, xref_cnt) \
        isa; /* must be pointer-sized */ \
        int volatile ref_cnt; \
        int volatile xref_cnt

把宏展开,得到直观的定义:

typedef struct _os_object_s {
    const _os_object_vtable_s *os_obj_isa; // 这个也是个宏定义,展开后似乎是可以被子类重写的和引用计数相关的两个函数指针
    int volatile os_obj_ref_cnt;     // 引用计数,这是内部gcd内部使用的计数器
    int volatile os_obj_xref_cnt;    // 外部引用计数,这是gcd外部使用的计数器,两者都为0的时候才能dispose
} _os_object_s;

dispatch_object_s

struct dispatch_object_s {
    _DISPATCH_OBJECT_HEADER(object);
};

#define _DISPATCH_OBJECT_HEADER(x) \
    struct _os_object_s _as_os_obj[0]; \
    OS_OBJECT_STRUCT_HEADER(dispatch_##x); \
    struct dispatch_##x##_s *volatile do_next; \
    struct dispatch_queue_s *do_targetq; \
    void *do_ctxt; \
    void *do_finalizer

宏展开:

struct dispatch_object_s {
    struct _os_object_s _as_os_obj[0];
    OS_OBJECT_STRUCT_HEADER(dispatch_object); // 继承自_os_object_s的部分,和引用计数相关
    struct dispatch_object_s *volatile do_next;  // 链表的 next
    struct dispatch_queue_s *do_targetq;  // 目标队列,指定这个object在哪个queue中执行
    void *do_ctxt;    // 上下文,我们要传递的参数
    void *do_finalizer;  // 析构函数
};

dispatch_queue_s

struct dispatch_queue_s {
    _DISPATCH_QUEUE_HEADER(queue);
} DISPATCH_ATOMIC64_ALIGN;

#define _DISPATCH_QUEUE_HEADER(x) \
    struct os_mpsc_queue_s _as_oq[0]; \
    DISPATCH_OBJECT_HEADER(x); \
    _OS_MPSC_QUEUE_FIELDS(dq, dq_state); \
    uint32_t dq_side_suspend_cnt; \
    dispatch_unfair_lock_s dq_sidelock; \
    union { \
        dispatch_queue_t dq_specific_q; \
        struct dispatch_source_refs_s *ds_refs; \
        struct dispatch_timer_source_refs_s *ds_timer_refs; \
        struct dispatch_mach_recv_refs_s *dm_recv_refs; \
    }; \
    DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \
        const uint16_t dq_width, \
        const uint16_t __dq_opaque \
    ); \

宏展开:

struct dispatch_queue_s {
    struct os_mpsc_queue_s _as_oq[0];
    DISPATCH_OBJECT_HEADER(x);
    _OS_MPSC_QUEUE_FIELDS(dq, dq_state); // 这里又是一个宏,需要再展开
    uint32_t dq_side_suspend_cnt;
    dispatch_unfair_lock_s dq_sidelock;
    union { 
        dispatch_queue_t dq_specific_q; 
        struct dispatch_source_refs_s *ds_refs; 
        struct dispatch_timer_source_refs_s *ds_timer_refs; 
        struct dispatch_mach_recv_refs_s *dm_recv_refs; 
    }; 
    DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags,
        const uint16_t dq_width, 
        const uint16_t __dq_opaque 
    ); 
}

#define _OS_MPSC_QUEUE_FIELDS(ns, __state_field__) \
    struct dispatch_object_s *volatile ns##_items_head; \
    DISPATCH_UNION_LE(uint64_t volatile __state_field__, \
            dispatch_lock __state_field__##_lock, \
            uint32_t __state_field__##_bits \
    ) DISPATCH_ATOMIC64_ALIGN; \
    /* LP64 global queue cacheline boundary */ \
    unsigned long ns##_serialnum; \
    const char *ns##_label; \
    struct dispatch_object_s *volatile ns##_items_tail; \
    dispatch_priority_t ns##_priority; \
    int volatile ns##_sref_cnt

展开_OS_MPSC_QUEUE_FIELDS 后:

struct dispatch_queue_s {
    struct os_mpsc_queue_s _as_oq[0];
    // 展开_OS_MPSC_QUEUE_FIELDS 
    struct dispatch_object_s *volatile queue_items_head; // queue首元素
    DISPATCH_UNION_LE(
            uint64_t volatile dq_state,    // queue的状态
            dispatch_lock dq_state_lock, 
            uint32_t dq_state_bits
    ) DISPATCH_ATOMIC64_ALIGN;
    unsigned long queue_serialnum; // queue的编号
    const char *queue_label; // queue的名称
    struct dispatch_object_s *volatile queue_items_tail;  // queue 尾元素
    dispatch_priority_t queue_priority; // queue优先级
    int volatile queue_sref_cnt

    uint32_t dq_side_suspend_cnt;
    dispatch_unfair_lock_s dq_sidelock;
    union { // 这些是提交的queue上的元素队列吗????????
        dispatch_queue_t dq_specific_q; 
        struct dispatch_source_refs_s *ds_refs; 
        struct dispatch_timer_source_refs_s *ds_timer_refs; 
        struct dispatch_mach_recv_refs_s *dm_recv_refs; 
    }; 
    DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags,
        const uint16_t dq_width,    // queue的并发数(dq_width==1表示是串行队列)
        const uint16_t __dq_opaque 
    ); 
}

dispatch_continuation_s

typedef struct dispatch_continuation_s {
    struct dispatch_object_s _as_do[0];
    DISPATCH_CONTINUATION_HEADER(continuation);
} *dispatch_continuation_t;

#define DISPATCH_CONTINUATION_HEADER(x) \
    union { \
        const void *do_vtable; \
        uintptr_t dc_flags; \
    }; \
    union { \
        pthread_priority_t dc_priority; \
        int dc_cache_cnt; \
        uintptr_t dc_pad; \
    }; \
    struct dispatch_##x##_s *volatile do_next; \
    struct voucher_s *dc_voucher; \
    dispatch_function_t dc_func; \
    void *dc_ctxt; \
    void *dc_data; \
    void *dc_other

宏展开后

typedef struct dispatch_continuation_s {
    struct dispatch_object_s _as_do[0];
    union {
        const void *do_vtable;
        uintptr_t dc_flags;
    };
    union { 
        pthread_priority_t dc_priority; 
        int dc_cache_cnt; 
        uintptr_t dc_pad; 
    };
    struct dispatch_continuation_s *volatile do_next;
    struct voucher_s *dc_voucher;
    dispatch_function_t dc_func;
    void *dc_ctxt;
    void *dc_data;
    void *dc_other

} *dispatch_continuation_t;

dispatch_queue_attr_s

struct dispatch_queue_attr_s {
    OS_OBJECT_STRUCT_HEADER(dispatch_queue_attr);
    dispatch_priority_requested_t dqa_qos_and_relpri;  // queue优先级
    uint16_t dqa_overcommit:2;                       // 是否可以overcommit
    uint16_t dqa_autorelease_frequency:2;
    uint16_t dqa_concurrent:1;                      // 是否是并发队列
    uint16_t dqa_inactive:1;                        // 是否激活
};

root queues

我们要使用GCD,需要把任务提交到队列。而我们可以用如下三种方法之一获取要使用的队列:

dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
        dispatch_queue_attr_t _Nullable attr)

dispatch_queue_t dispatch_get_main_queue(void)

dispatch_queue_t
dispatch_get_global_queue(long identifier, unsigned long flags)

第一种方法是创建一个queue,后两种方法是获取系统自定义的queue。

但其实背后的实现是,无论是自己创建还是获取系统定义的queue,只会在GCD启动时创建的root queue数组中,取得一个queue而已。

用户层面的queue和root queue之间的关系如下图所示:
TODO

root queue一共有12个,分别有不同的优先级和序列号。

让我们一起看看它们的实现,首先,是dispatch_queue_create

dispatch_queue_create

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

可以看到,当用户要创建一个queue的时候,需要指定target queue,即创建的queue最终是在哪个queue上执行的,这称之为target queue。对于用户创建的queue来说,这个target queue会取root queue之一。

这里的DISPATCH_TARGET_QUEUE_DEFAULT是一个宏定义:

#define DISPATCH_TARGET_QUEUE_DEFAULT NULL
static dispatch_queue_t
_dispatch_queue_create_with_target(const char *label, dispatch_queue_attr_t dqa,
        dispatch_queue_t tq, bool legacy)
{
    if (!slowpath(dqa)) { // 如果是串行队列
        dqa = _dispatch_get_default_queue_attr(); // 串行队列的attr 取默认attr
    } else if (dqa->do_vtable != DISPATCH_VTABLE(queue_attr)) { // 并行队列的 attr->do_vtable 应该等于 DISPATCH_VTABLE(queue_attr)
        DISPATCH_CLIENT_CRASH(dqa->do_vtable, "Invalid queue attribute");
    }

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

    // dispatch_qos_t qos是优先级?
    dispatch_qos_t qos = _dispatch_priority_qos(dqa->dqa_qos_and_relpri);
    // 是否overcommit(即queue创建的线程数是否允许超过实际的CPU个数)
    _dispatch_queue_attr_overcommit_t overcommit = dqa->dqa_overcommit;
    if (overcommit != _dispatch_queue_attr_overcommit_unspecified && tq) { //
        if (tq->do_targetq) { // overcommit 的queue 必须是全局的
            DISPATCH_CLIENT_CRASH(tq, "Cannot specify both overcommit and "
                    "a non-global target queue");
        }
    }

    // 下面这些代码,因为用户创建的queue的tq一定为NULL,因此,只要关注tq == NULL的分支即可,我们删除了其余分支
    if (!tq) { // 自己创建的queue,tq都是null
        tq = _dispatch_get_root_queue( // 在root queue里面去取一个合适的queue当做target queue
                qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, // 无论是用户创建的串行还是并行队列,其qos都没有指定,因此,qos这里都取DISPATCH_QOS_DEFAULT
                overcommit == _dispatch_queue_attr_overcommit_enabled);
        if (slowpath(!tq)) { // 如果根据create queue是传入的属性无法获取到对应的tq,crash
            DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
        }
    }

    //
    // Step 2: Initialize the queue
    //
    const void *vtable;
    dispatch_queue_flags_t dqf = 0;
    // 根据不同的queue类型,设置vtable。vtable实现了SERIAL queue 和 CONCURRENT queue的行为差异。
    if (dqa->dqa_concurrent) { // 并发
        vtable = DISPATCH_VTABLE(queue_concurrent);
    } else {  // 串行
        vtable = DISPATCH_VTABLE(queue_serial);
    }

    // 创建一个与tq对应的dq,用来给用户返回
    dispatch_queue_t dq = _dispatch_object_alloc(vtable,
            sizeof(struct dispatch_queue_s) - DISPATCH_QUEUE_CACHELINE_PAD);
    // 初始化dq,可以看到dqa->dqa_concurrent,对于并发队列,其queue width是DISPATCH_QUEUE_WIDTH_MAX,而串行队列其width是1
    _dispatch_queue_init(dq, dqf, dqa->dqa_concurrent ? 
            DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
            (dqa->dqa_inactive ? DISPATCH_QUEUE_INACTIVE : 0));

    dq->dq_label = label; // 设置dq的名字
#if HAVE_PTHREAD_WORKQUEUE_QOS
    dq->dq_priority = dqa->dqa_qos_and_relpri;
    if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
        dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
    }
#endif
    _dispatch_retain(tq);
    if (qos == QOS_CLASS_UNSPECIFIED) { // 如果没有指定queue的优先级,则默认继承target queue的优先级
        // legacy way of inherithing the QoS from the target
        _dispatch_queue_priority_inherit_from_target(dq, tq);
    }
    if (!dqa->dqa_inactive) {
        _dispatch_queue_inherit_wlh_from_target(dq, tq);
    }
    dq->do_targetq = tq; // 这一步,很关键!!! 将root queue设置为dq的target queue,root queue和新创建的queue联合在了一起
    _dispatch_object_debug(dq, "%s", __func__);
    // 将新创建的dq,添加到GCD内部管理的叫做_dispatch_introspection的queue列表中。这是GCD内部维护的一个queue列表,具体作用不太清楚。
    return _dispatch_introspection_queue_create(dq);
}

去除多余的判断,其实逻辑也很简单,当我们调用dispatch_queue_create,GCD内部会调用_dispatch_queue_create_with_target, 它首先会根据我们创建的queue的属性:DISPATCH_QUEUE_SERIALDISPATCH_QUEUE_CONCURRENT,到root queue数组中取出一个对应的queue作为target queue。然后,会新建一个dispatch_queue_t对象,并设置其target queue,返回给用户。同时,在GCD内部,新建的queue还会被加入introspection queue列表中。

这里的关键是根据queue属性获取对应的target root queue:

tq = _dispatch_get_root_queue( // 在root queue里面去取一个合适的queue当做target queue
                qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, // 串行 用DISPATCH_QOS_DEFAULT, 并行 用自己的qos

_dispatch_get_root_queue的实现如下:

static inline dispatch_queue_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
    return &_dispatch_root_queues[2 * (qos - 1) + overcommit]; // 根据qos和 是否overcommit,取得root queues数组中对应的queue(一共有12个root queue)
}

qos==DISPATCH_QOS_DEFAULT,值是4。根据公式,2*(4-1) + 1 = 7, 所有创建的串行队列应该取root queue数组的第8个queue,而由于并行队列的overcommit是false(0),并行队列回去root queue的第7个元素:

// 用户创建的并行队列,默认会使用这个target queue
    _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_DEFAULTQUEUE,
        .dq_label = "com.apple.root.default-qos",
        .dq_serialnum = 10,
    ),
    // 用户创建的串行队列,会使这个target queue, main queue 也会取这个值,但是会将 .dq_label = "com.apple.main-thread",
                                                                        // .dq_atomic_flags = DQF_THREAD_BOUND | DQF_CANNOT_TRYSYNC | DQF_WIDTH(1),
                                                                        // .dq_serialnum = 1,
    _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
            DISPATCH_PRIORITY_FLAG_DEFAULTQUEUE | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.default-qos.overcommit",
        .dq_serialnum = 11,
    ),

OK,现在我们已经知道用户自创建的queue默认都会附加到root queue上。那么对于dispatch_get_main_queue, dispatch_get_global_queue是否也有类似的逻辑呢?我们先来看dispatch_get_global_queue

dispatch_get_global_queue

我们通常会用如下代码,来获取一个全局的并发队列,并指定其优先级。

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

其实现如下:

dispatch_queue_t
dispatch_get_global_queue(long priority, unsigned long flags)
{
    if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {
        return DISPATCH_BAD_INPUT;
    }
    dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority); // 将用户使用的优先级转换为root queue的优先级
    if (qos == DISPATCH_QOS_UNSPECIFIED) {
        return DISPATCH_BAD_INPUT;
    }
    return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT); // 由于flags是保留值,均取0,因此global queue都是no overcommit的
}

逻辑很简单,首先将外部传入的queue优先级转换为GCD内部的优先级dispatch_qos_t qos。 然后,在调用_dispatch_get_root_queue 获取root queue中对应的queue。

我们来看一下_dispatch_qos_from_queue_priority的实现:

static inline dispatch_qos_t
_dispatch_qos_from_queue_priority(long priority)
{
    switch (priority) {
    case DISPATCH_QUEUE_PRIORITY_BACKGROUND:      return DISPATCH_QOS_BACKGROUND;
    case DISPATCH_QUEUE_PRIORITY_NON_INTERACTIVE: return DISPATCH_QOS_UTILITY;
    case DISPATCH_QUEUE_PRIORITY_LOW:             return DISPATCH_QOS_UTILITY;
    case DISPATCH_QUEUE_PRIORITY_DEFAULT:         return DISPATCH_QOS_DEFAULT;
    case DISPATCH_QUEUE_PRIORITY_HIGH:            return DISPATCH_QOS_USER_INITIATED;
    default: return _dispatch_qos_from_qos_class((qos_class_t)priority);
    }
}

dispatch_get_main_queue

/*!
 * @function dispatch_get_main_queue
 *
 * @abstract
 * Returns the default queue that is bound to the main thread.
 *
 * @discussion
 * In order to invoke blocks submitted to the main queue, the application must
 * call dispatch_main(), NSApplicationMain(), or use a CFRunLoop on the main
 * thread.
 *
 * @result
 * Returns the main queue. This queue is created automatically on behalf of
 * the main thread before main() is called.
 */
dispatch_queue_t
dispatch_get_main_queue(void)
{
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
}

根据注释,可以看到,main queue是由系统在main()方法调用前创建的。专门绑定到main thread上。而且,为了触发提交到main queue上的block,和其他queue不一样,main queue上的任务是依赖于main runloop触发的

_dispatch_main_q

struct dispatch_queue_s _dispatch_main_q = {
    DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
    .do_targetq = &_dispatch_root_queues[
            DISPATCH_ROOT_QUEUE_IDX_DEFAULT_QOS_OVERCOMMIT],  // 同样也是取root queue中的queue作为target queue
#endif
    .dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
            DISPATCH_QUEUE_ROLE_BASE_ANON,
    .dq_label = "com.apple.main-thread",
    .dq_atomic_flags = DQF_THREAD_BOUND | DQF_CANNOT_TRYSYNC | DQF_WIDTH(1),
    .dq_serialnum = 1,
};

总结

从上面源码可以看出,GCD用到的queue,无论是自己创建的,或是获取系统的main queue还是global queue,其最终都是落脚于GCD root queue中。 我们可以在代码中的任意位置创建queue,但最终GCD管理的,也不过这12个root queue,这种思路有点类似于命令模式,即分散创建,集中管理

猜你喜欢

转载自blog.csdn.net/u013378438/article/details/81031938
今日推荐