附录-OC源码-GCD:`数据结构` 源码解析

使用版本:
libdispatch-1173.40.5.tar
使用部分网上解析作参考, 但因为版本问题, 很多不一样, 这里主要针对当前使用的源码
网上有篇叫 , 感觉全网都是抄他的, 然而里面除了列出源码啥都没讲

主要解析以下这些常用的结构体, 源码中涉及的结构体太多了, 而且 极绕!, 暂时不深究其他

  • dispatch_object_s
  • dispatch_queue_s
  • dispatch_queue_attr_t
  • dispatch_group_s
  • dispatch_source_s
  • dispatch_semaphore_s
    这些结构体内部都会涉及一个宏, 用于描述其 isa, 我归纳到这里其他: GCD 中 isa 中有什么

基础知识

  • C 中的宏#,##的含义:
    前者作用是转为字符串, 后者作用是将东西黏在一起, 具体参考见: C语言宏定义中#和##的作用
    # : #define str(s) #s => str(hello) 转换为"hello"
    ##: #define v(a,b,c,d) 0xa##b##c##d => v(CF,AB,B0,BC) 准换为 0xCFABB0BC

  • C 中使用宏来做代码拼接, 在用法上可以模拟出我们理解的继承概念(子类拥有父类定义过的字段), 在宏定义时, 只要将struct的字段用宏一层层定义即可. 下层定义最基础字段, 上层引用了这个宏, 就会带进来这些字段. 不同的 struct内部包含相同的宏,就会有同样的字段
    (当然, 和继承不一样的是, 宏的写法可自由了, 而用宏不用继承的原因,是继承会带来额外的性能消耗?)

  • GCD 源码中数据结构极绕!, 涉及大量宏的使用, 每一个基本的 struct 都是由宏一层层拼接的, 这里以 dispatch_object_s 举例, 描述整个追溯过程.
    下面提到的几个宏, 在源码中会经常出现, 使用相同宏的 struct 证明其中部分数据结构类似
    这里涉及几个宏, 我写在这里 其他: 数据结构中使用的宏
    (ps. 这里面的宏包含了之后结构体都会有的通用字段
    下面拼接一次, 后面讲结构体都直接放上拼接后的内容, 不再一步步拼接
    1.定义的数据结构

    struct dispatch_object_s {  
    _DISPATCH_OBJECT_HEADER(object);  
    };  
    

    2.拼接 _DISPATCH_OBJECT_HEADER

    #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);  
        
      struct dispatch_object_s *volatile do_next;  
      struct dispatch_queue_s *do_targetq;  
      void *do_ctxt;  
      void *do_finalizer  
    };  
    

    3.拼接 OS_OBJECT_STRUCT_HEADER

    #define OS_OBJECT_STRUCT_HEADER(x) \  
    _OS_OBJECT_HEADER(\  
    const struct x##_vtable_s *do_vtable, \  
    do_ref_cnt, \  
    do_xref_cnt)  
    #endif  
    

    展开后:

    // ↓ dispatch_object_s  
    struct dispatch_object_s {  
      struct _os_object_s _as_os_obj[0];   
      _OS_OBJECT_HEADER(  
          const struct dispatch_object_vtable_s *do_vtable, \  
          do_ref_cnt,   
          do_xref_cnt  
      )  
      
      struct dispatch_object_s *volatile do_next;  
      struct dispatch_queue_s *do_targetq;  
      void *do_ctxt;  
      void *do_finalizer  
    };  
    

    4.展开 _OS_OBJECT_HEADER

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

    展开后: (有的文章中多出个 isa, 我自己推下来do_vtable就是这个 isa, 实际看其他部分源码, 也确实如此.)

    struct dispatch_object_s {  
      struct _os_object_s _as_os_obj[0];   
      
      const struct dispatch_object_vtable_s *do_vtable  
      int volatile ref_cnt;  
      int volatile xref_cnt  
    	  
      struct dispatch_object_s *volatile do_next;  
      struct dispatch_queue_s *do_targetq;  
      void *do_ctxt;  
      void *do_finalizer  
    };  
    

dispatch_object_t

typedef union {  
	struct _os_object_s *_os_obj;  
	struct dispatch_object_s *_do;		//常用的 GCD 结构体  
	struct dispatch_queue_s *_dq;			// 队列  
	struct dispatch_queue_attr_s *_dqa;	// 队列属性  
	struct dispatch_group_s *_dg;			// group  
	struct dispatch_source_s *_ds;		//source  
	struct dispatch_channel_s *_dch;  
	struct dispatch_mach_s *_dm; 			 //mach 内核  
	struct dispatch_mach_msg_s *_dmsg;	 // mach 内核相关  
	struct dispatch_semaphore_s *_dsema;	  //信号量  
	struct dispatch_data_s *_ddata;  
	struct dispatch_io_s *_dchannel;  
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;  

可以理解为 dispatch_object_t, 是为了解决语法中类型问题, 用一个大的结构体, 封装了GCD 中常用的基本类型
(类似于函数接口或者返回值用父类,可以传入子类或返回任何子类的操作)
其内部的 dispatch_object_s, dispatch_queue_s, dispatch_source_s, dispatch_semaphore_s
就是我们使用 GCD 常见的那几个

后面会发现, 这些基本的数据结构, 都包含一系列通用字段, 这篇文章里, 我对他们的 isa 做了解析 其他: GCD 中 isa 中有什么
而通用的宏提取到这篇文章中 其他: 数据结构中使用的宏, 之后也不会再细讲
DISPATCH_OBJECT_HEADER -> _DISPATCH_OBJECT_HEADER -> OS_OBJECT_STRUCT_HEADER -> _OS_OBJECT_HEADER
(简单的理解(不完全准确), 就是从父类继承的意思)

union 在 runtime, runloop 都介绍过, dispatch_object_t 结构体
包含内部任意的其中一个的字段, 并且不管是哪个字段,地址都是同一个.

内核相关的内容见之前我在 runloop 中提到的 其他: Mach 是什么, 不做过深探究


dispatch_object_s

探究过程已经在上面作为例子了
主要是宏 _DISPATCH_OBJECT_HEADER, 提取到这篇文章中 其他: 数据结构中使用的宏
后面不再列出

  • _as_os_obj:暂时未知
  • do_vtable: 以宏解析过程的名字看,是 isa, isa 在runtime, runloop 源码都有使用,一般指向其存放了信息的实体(类,元类)
  • do_ref_cnt: 内部引用计数
  • do_xref_cnt: 外部引用计数
  • do_next: 很明显的链表节点
  • do_targetq: 执行线程
  • do_ctxt: 上下文 (望名猜意, 未求证)
  • do_finalizer: 析构 (望名猜意, 未求证)
    struct dispatch_object_s {  
      struct _os_object_s _as_os_obj[0];  
      
      const struct dispatch_object_vtable_s *do_vtable;  
      int volatile do_ref_cnt;  
      int volatile do_xref_cnt;  
      
      struct dispatch_object_s *volatile do_next;  
      struct dispatch_queue_s *do_targetq;  
      void *do_ctxt;  
      void *do_finalizer;  
    };  
    

dispatch_queue_s: GCD 调用线程必用的dispatch_queue_t

调用 GCD 必备的指针 dispatch_queue_t 的结构体类型,就是他

DISPATCH_DECL(dispatch_queue);  
#define DISPATCH_DECL(name) typedef struct name##_s *name##_t  
typedef struct dispatch_queue_s *dispatch_queue_t;  

涉及的宏 DISPATCH_QUEUE_CLASS_HEADER -> _DISPATCH_QUEUE_CLASS_HEADER -> DISPATCH_OBJECT_HEADER -> _DISPATCH_OBJECT_HEADER -> OS_OBJECT_STRUCT_HEADER -> _OS_OBJECT_HEADER

DISPATCH_OBJECT_HEADER开始写在 其他: 数据结构中使用的宏, 包含 isa, 链表结构等不再细讲, 和上面的dispatch_object_s除了一个字段外,基本一样

do_ctxt: 在我追踪 root_queue 时发现,存的就是线程池, 其宗旨关联到dispatch_pthread_root_queue_context_s
内部包含了一个线程数组 pthread_attr_t (也可能不是pthread, 由宏控制的,宏在 CMake 中又由别的宏控制)

DISPATCH_QUEUE_CLASS_HEADER, 多出以下字段:

  • dq_serialnum: 源码中初始化时, 会创建很多的queue, 然后给这些 queue 指定序号,放在这个字段
    init.c
  • dq_label: 创建线程时的标识符
  • dq_width: 线程并发数
  • __dq_opaque2: ❓未知, (只见定义, 源码中没看到任何用的地方)
  • dq_priority: 优先级
  • ❓剩余字段用处未明晰
    unsigned long dq_serialnum; \  
    const char *dq_label; \  
    DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \  
      const uint16_t dq_width, \  
      const uint16_t __dq_opaque2 \  
    ); \  
    dispatch_priority_t dq_priority; \  
    union { \  
      struct dispatch_queue_specific_head_s *dq_specific_head; \  
      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; \  
      struct dispatch_channel_callbacks_s const *dch_callbacks; \  
    }; \  
    int volatile dq_sref_cnt  
    
  • 最终展开如下
    struct dispatch_queue_s {  
    	  
      struct dispatch_object_s _as_do[0];   
      struct _os_object_s _as_os_obj[0];   
    	  
      const struct dispatch_queue_vtable_s *do_vtable,   
      int volatile do_ref_cnt,   
      int volatile do_xref_cnt  
      
      struct dispatch_queue_s *volatile do_next;   
      struct dispatch_queue_s *do_targetq;   
      void *do_ctxt;   
      void *do_finalizer  
      
      DISPATCH_UNION_LE(uint64_t volatile dq_state,   
              dispatch_lock dq_state_lock,   
              uint32_t dq_state_bits   
      )  
      
      /* LP64 global queue cacheline boundary */   
      unsigned long dq_serialnum;   
      const char *dq_label;   
      DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags,   
          const uint16_t dq_width,   
          const uint16_t __dq_opaque2   
      );   
      
      /* 32bit hole on LP64 */  
    } DISPATCH_ATOMIC64_ALIGN;  
    

dispatch_queue_attr_s: dispatch_queue_create的入参

dispatch_queue_create 的其中一个入参, dispatch_queue_attr_t 与他的关系

DISPATCH_DECL(dispatch_queue_attr);  
#define DISPATCH_DECL(name) typedef struct name##_s *name##_t  
typedef struct dispatch_queue_attr_s * dispatch_queue_attr_t;  

具体结构和之前一样是通用的OS_OBJECT_STRUCT_HEADER , 归纳在这 其他: 数据结构使用的宏(都有的字段)

struct dispatch_queue_attr_s {  
  OS_OBJECT_STRUCT_HEADER(dispatch_queue_attr);  
};  

dispatch_group_s

常用的 dispatch_group_t 的结构体

DISPATCH_DECL(dispatch_group);  
#define DISPATCH_DECL(name) typedef struct name##_s *name##_t  
typedef struct dispatch_group_s * dispatch_group_t;  

DISPATCH_OBJECT_HEADER其他: 数据结构使用的宏(都有的字段) 不再细讲
可以看到多出来的字段明显是个链表结构:

  • dg_notify_head: 头结点
  • dg_notify_tail: 尾结点
    联想我们的使用流程, 不难得出, 这是 dispatch_group_s 在执行完后, 调用 notify 时的链表
  • dg_state: 字面意思描述状态用的, 用于在内部函数中判断用
  • dg_bits: 对应使用的函数是dispatch_group_enter, dispatch_group_leave, 看的出来是防止异常的enterleave不匹配而设置的变量
    struct dispatch_group_s {  
    DISPATCH_OBJECT_HEADER(group);  
    DISPATCH_UNION_LE(uint64_t volatile dg_state,  
        uint32_t dg_bits,  
        uint32_t dg_gen  
    ) DISPATCH_ATOMIC64_ALIGN;  
    struct dispatch_continuation_s *volatile dg_notify_head;  
    struct dispatch_continuation_s *volatile dg_notify_tail;  
    };  
    

dispatch_semaphore_s: 信号量

同上 DISPATCH_OBJECT_HEADER其他: 数据结构使用的宏(都有的字段)

  • dsema_value: 使用信号量时创建的 量 值就存在这, 具体调度在 dispatch_semaphore_create
  • dsema_orig: 同上, 与上面不同的是, 上面记录当前值, 这个记录原始值
  • dsema_sema: 暂时没从函数中看出来是干嘛的, 有文章提到与semaphore_create相关, 从名字看也确实应该相关, 但我从源码中看不出来
    struct dispatch_semaphore_s {  
    DISPATCH_OBJECT_HEADER(semaphore);  
    long volatile dsema_value;  
    long dsema_orig;  
    _dispatch_sema4_t dsema_sema;  
    };  
    
    dispatch_semaphore_t dispatch_semaphore_create(long value) {  
      //省略大部分  
    dsema->dsema_value = value;  
      _dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);  
    dsema->dsema_orig = value;  
    }  
    

dispatch_continuation_s: 所有执行体的封装

在源码实现过程中会发现很多很多地方用到这个结构体, 这里先直接给出结论, 他就是 GCD dispatch_async dispatch_group_notify 等操作传入的 block 的封装.
其中涉及到的宏 DISPATCH_CONTINUATION_HEADER 有很多总定义,这里只取 __LP64__下的

#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  

最终展开为

  • dc_func: 根据很多函数中查找的结果, 是最终执行函数的指针_dispatch_block_async_invoke2
  • 有几个字段其他结构体中也有 do_vtable,do_next, dc_ctxt
  • ❓其他内容源码中暂时推断不出作用
  • dispatch_function_t 是个宏 typedef void (*dispatch_function_t)(void *_Nullable);
    很单纯的函数指针
    typedef struct dispatch_continuation_s {  
     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_static_s 用于描述主线程的结构体

惊喜吧, 主线程和普通线程在底层是不一样的结构体

struct dispatch_queue_static_s {  
  struct dispatch_lane_s _as_dl[0]; \  
  DISPATCH_LANE_CLASS_HEADER(lane);  
} DISPATCH_CACHELINE_ALIGN;  

dispatch_queue_s 一样拥有 DISPATCH_QUEUE_CLASS_HEADER
多出了 dq_items_head

#define DISPATCH_LANE_CLASS_HEADER(x) \  
  struct dispatch_queue_s _as_dq[0]; \  
  DISPATCH_QUEUE_CLASS_HEADER(x, \  
      struct dispatch_object_s *volatile dq_items_tail); \  
  dispatch_unfair_lock_s dq_sidelock; \  
  struct dispatch_object_s *volatile dq_items_head; \  
  uint32_t dq_side_suspend_cnt