GCD源码分析(一)——“对象”和数据结构

一、前言

Grand Central Dispatch(GCD)作为iOS和MacOS开发中不可或缺的工具,它的重要性不言而喻。作为一个喜欢刨根问底的猿,知其然更要知其所以然。网上关于GCD解析的文章不多,仅有的几篇使用的源码版本很老,libdispatch从187.10发展到913.60,其内部已经发生了很多变化。趁工作之余断断续续把GCD的源码libdispatch-913.60.2.tar.gz看了一下,算是对它的底层原理有了一个大致的了解。

本篇作为系列文章的第一篇,先讲一下GCD中“对象”和数据结构。

为什么“对象”加引号

libdispatch库是纯C语言编写的,并不存在类似面向对象语言中的类、对象之类的概念,它的所有对象(如:dispatch_object_s, dispatch_queue_s, dispatch_semaphore_s等)都是定义为struct(dispatch_object_t是union类型)。
为了实现类似面向对象中的“继承”效果,GCD使用了若干个DISPATCH_XXX_HEADER宏,将“基类”的内容在了“子类”结构体内存布局的起始处重写了一次,实现了类似“继承”的概念。注意,这里的继承与OOP中的继承不一样,看不到诸如extends/:之类的继承符号的。

二、结构分析

libdispatch中定义了很多结构体,这里只选取比较常用的几个,不想看过程的同学可以下面的代码分析。
先上图,GCD中的继承结构:

GCD中的继承结构

2.1 dispatch_object_t

从上图可以看到,dispatch_object_t可以看做GCD中所有“类”的“基类”,看下其代码结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;

dispatch_object_t被定义为union,熟悉C语言的同学应该知道:union可以表示任意它里面定义的数据类型,union的大小为这些数据类型中最大的数据类型的大小。这里利用了union的特性将dispatch_object_t定义为所有子类:dispatch_xxx_s的基类。

2.2 _os_object_s

1
2
3
4
5
6
7
8
9
typedef struct _os_object_s {
const _os_object_vtable_s *isa;
int volatile os_obj_ref_cnt;
int volatile os_obj_xref_cnt;
} _os_object_s;

typedef struct _os_object_vtable_s {
const void *_os_obj_objc_isa;
} _os_object_vtable_s;
  • isa指针,应该类似于OC对象中的isa指针,用于判断当前dispatch object的类型,从下面的代码中可以看出一二:
1
2
3
4
5
6
7
8
9
10
11
12
void *_dispatch_object_alloc(const void *vtable, size_t size)
{
#if OS_OBJECT_HAVE_OBJC1
const struct dispatch_object_vtable_s *_vtable = vtable;
dispatch_object_t dou;
dou._os_obj = _os_object_alloc_realized(_vtable->_os_obj_objc_isa, size);
dou._do->do_vtable = vtable;
return dou._do;
#else
return _os_object_alloc_realized(vtable, size);
#endif
}
  • os_obj_ref_cnt/os_obj_xref_cnt,从命名方式猜测,应该是引用计数,与GCD的内存管理相关。从代码中的备注发现,二者分别为object在GCD内部和外部使用的引用计数。

2.3 dispatch_object_s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct dispatch_object_s {
struct _os_object_s _as_os_obj[0];
/// 继承自_os_object_s结构体的内容,包含了isa指针和引用计数
const void *isa;
int volatile ref_cnt;
int volatile xref_cnt;

const struct dispatch_object_vtable_s *do_vtable;
// object链表中的下一个元素
struct dispatch_object_s *volatile do_next;
// 目标队列,指定当前object的执行队列
struct dispatch_queue_s *do_targetq;
void *do_ctxt;
void *do_finalizer
};

上述为宏替换后的代码,原始代码中dispatch_object_s只有_DISPATCH_OBJECT_HEADER(object)

2.4 dispatch_queue_s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
struct dispatch_queue_s {
struct os_mpsc_queue_s _as_oq[0];
struct dispatch_object_s _as_do[0];
/// 继承自dispatch_object_s结构体的内容
struct _os_object_s _as_os_obj[0];
const void *isa;
int ref_cnt;
int xref_cnt;

const struct dispatch_queue_vtable_s *do_vtable;
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
) DISPATCH_ATOMIC64_ALIGN;
// queue的首元素
struct dispatch_object_s *volatile dq_items_head;
// queue编号
unsigned long dq_serialnum;
// queue名称
const char *dq_label;
// queue的尾元素
struct dispatch_object_s *volatile dq_items_tail;
// queue的优先级
dispatch_priority_t dq_priority;
// queue的reference count
int volatile dq_sref_cnt;

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, // 应该是队列的并发数,当为1时表示串行队列
const uint16_t __dq_opaque
);
DISPATCH_INTROSPECTION_QUEUE_HEADER

char _dq_pad[DISPATCH_QUEUE_CACHELINE_PAD]; // for static queues only
} DISPATCH_ATOMIC64_ALIGN;

2.5 dispatch_continuation_s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct dispatch_continuation_s {
struct dispatch_object_s _as_do[0];
// 任务的函数地址
dispatch_function_t dc_func;
union {
// 优先级
pthread_priority_t dc_priority;
int dc_cache_cnt;
uintptr_t dc_pad;
};
struct voucher_s *dc_voucher;
union {
const void *do_vtable;
uintptr_t dc_flags;
};
// 任务链表中的下一个元素
struct dispatch_continuation_s *volatile do_next;
// 上下文
void *dc_ctxt;
void *dc_data;
void *dc_other
} *dispatch_continuation_t;

从结构体中的dispatch_function_t类型,可以猜测dispatch_continuation_s与GCD的任务有关,我们向GCD中提交的任务,无论是Block或者函数形式,最终都转化成dispatch_continuation_s

2.6 dispatch_group_s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct dispatch_group_s {
/// 继承自dispatch_object_s的内容
struct dispatch_object_s _as_do[0];
struct _os_object_s _as_os_obj[0];
const void *_objc_isa;
int volatile do_ref_cnt;
int volatile do_xref_cnt;
const struct dispatch_group_vtable_s *do_vtable;

struct dispatch_group_s *volatile do_next;
// group执行的目标队列
struct dispatch_queue_s *do_targetq;
void *do_ctxt;
void *do_finalizer;

// 标识符或者当前group中执行的任务数,0:当前group未在执行,非0:当前group正在执行
long volatile dg_value;
_dispatch_sema4_t dg_sema;
int volatile dg_waiters;

struct dispatch_continuation_s *volatile dg_notify_head;
struct dispatch_continuation_s *volatile dg_notify_tail;
};

与其他结构体内容大同小异,需要关注的是这两个参数:

  • dg_notify_head
  • dg_notify_tail

我们知道dispatch_group_notify可以用来做线程同步,那么猜测猜测这两个变量与实现dispatch_group_notify功能相关,当group中内容执行完毕后,通知dg_notify链表中的任务,_head_tail用于定位这些需要接收通知的任务的位置。

在semaphore.c中找到相关源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
static inline void
_dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq,
dispatch_continuation_t dsn)
{
dsn->dc_data = dq;
dsn->do_next = NULL;
_dispatch_retain(dq);
if (os_mpsc_push_update_tail(dg, dg_notify, dsn, do_next)) {
// retain group
_dispatch_retain(dg);
// 调用atomic_store_explicit函数将当前任务(dsn)替换为dg->dg_notify_head,
// 该操作为原子操作
atomic_store_explicit(((typeof(*(&(dg)->dg_notify_head)) _Atomic *)(&(dg)->dg_notify_head)), dsn, memory_order_ordered)

// seq_cst with atomic store to notify_head <rdar://problem/11750916>
// 判断当前group中是否有正在执行的任务,如果没有,调用_dispatch_group_wake函数唤醒
if (atomic_load_explicit(((typeof(*(&(dg)->dg_value)) _Atomic *)(&(dg)->dg_value)), memory_order_ordered)) == 0) {
// 唤醒group
_dispatch_group_wake(dg, false);
}
}
}

static long
_dispatch_group_wake(dispatch_group_t dg, bool needs_release)
{
dispatch_continuation_t next, head, tail = NULL;
long rval;

// cannot use os_mpsc_capture_snapshot() because we can have concurrent
// _dispatch_group_wake() calls

// 将head设置成dg->dg_notify_head,然后清空dg->dg_notify_head的值
head = atomic_exchange_explicit(((typeof(*(&(dg)->dg_notify_head)) _Atomic *)(&(dg)->dg_notify_head)), NULL, memory_order_relaxed);
if (head) {
// snapshot before anything is notified/woken <rdar://problem/8554546>
// 将tail设置成dg->dg_notify_tail,然后清空dg->dg_notify_tail的值
tail = atomic_exchange_explicit(((typeof(*(&(dg)->dg_notify_tail)) _Atomic *)(&(dg)->dg_notify_tail)), NULL, memory_order_release);
}

rval = (long)atomic_exchange_explicit(((typeof(*(&(dg)->dg_waiters)) _Atomic *)(&(dg)->dg_waiters)), 0, memory_order_relaxed);
if (rval) {
// wake group waiters
_dispatch_sema4_create(&dg->dg_sema, _DSEMA4_POLICY_FIFO);
// 根据waiters的数量创建相应数量的信号量
_dispatch_sema4_signal(&dg->dg_sema, rval);
}
uint16_t refs = needs_release ? 1 : 0; // <rdar://problem/22318411>
if (head) {
// async group notify blocks
// 异步通知每一个任务
do {
next = os_mpsc_pop_snapshot_head(head, tail, do_next);
dispatch_queue_t dsn_queue = (dispatch_queue_t)head->dc_data;
_dispatch_continuation_async(dsn_queue, head);
_dispatch_release(dsn_queue);
} while ((head = next));
refs++;
}
if (refs) _dispatch_release_n(dg, refs);
return 0;
}

2.7 dispatch_source_s

1
2
3
4
5
6
7
8
9
10
11
12
13
struct dispatch_source_s {
struct dispatch_queue_s _as_dq[0];
_DISPATCH_QUEUE_HEADER(source)
unsigned int
ds_is_installed:1,
dm_needs_mgr:1,
dm_connect_handler_called:1,
dm_uninstalled:1,
dm_cancel_handler_called:1,
dm_is_xpc:1
uint64_t ds_data DISPATCH_ATOMIC64_ALIGN;
uint64_t ds_pending_data DISPATCH_ATOMIC64_ALIGN;
} DISPATCH_ATOMIC64_ALIGN;

dispatch_source_s继承自dispatch_queue_s,从字面意思可称为调度源,它的作用是当有一些特定的较底层的系统事件发生时,调度源会捕捉到这些事件,然后可以做其他的逻辑处理,调度源有多种类型,分别监听对应类型的系统事件。GCD提供了六大类调度源:

  • Timer Dispatch Source:定时调度源。
  • Signal Dispatch Source:监听UNIX信号调度源,比如监听代表挂起指令的SIGSTOP信号。
  • Descriptor Dispatch Source:监听文件相关操作和Socket相关操作的调度源。
  • Process Dispatch Source:监听进程相关状态的调度源。
  • Mach port Dispatch Source:监听Mach相关事件的调度源。
  • Custom Dispatch Source:监听自定义事件的调度源。

可以监听12个类型的事件(可以在source.h文件中查看):

  • DISPATCH_SOURCE_TYPE_DATA_ADD,自定义事件,可以通过dispatch_source_get_data函数获取事件变量数据,在我们自定义的方法中可以调用dispatch_source_merge_data函数向调度源设置数据
  • DISPATCH_SOURCE_TYPE_DATA_OR,同上
  • DISPATCH_SOURCE_TYPE_DATA_REPLACE,同上
  • DISPATCH_SOURCE_TYPE_MACH_SEND,Mach内核端口发送事件
  • DISPATCH_SOURCE_TYPE_MACH_RECV,Mach内核端口监听事件
  • DISPATCH_SOURCE_TYPE_MEMORYPRESSURE,内存压力事件,分为三个等级:NORMAL、WARN、CRITICAL
  • DISPATCH_SOURCE_TYPE_PROC,进程相关事件,如进程退出、创建子线程、收到UNIX信号等事件
  • DISPATCH_SOURCE_TYPE_READ,IO读操作事件,如文件或socket的读操作
  • DISPATCH_SOURCE_TYPE_WRITE,IO写操作事件
  • DISPATCH_SOURCE_TYPE_SIGNAL,进程接收Unix内核信号事件
  • DISPATCH_SOURCE_TYPE_TIMER,定时器事件
  • DISPATCH_SOURCE_TYPE_VNODE,文件状态改变事件,如文件移动、删除、重命名等