cocoa_chen

深入浅出GCD之基础篇

2018-03-01

GCD介绍

Grand Central Dispatch(GCD)是Apple推出的一套多线程解决方案,它拥有系统级的线程管理机制,开发者不需要再管理线程的生命周期,只需要关注于要执行的任务即可。

本系列文章会从源码层面分析GCD的原理,总结GCD的用法和需要注意的地方,因此后续的文章都会从使用篇->原理篇->总结篇来讲,不太关心原理的可重点看下开头的使用篇和结尾的总结篇

GCD的源码libdispatch版本很多,源代码风格各版本都有不同,但大体逻辑没有太大变化。libdispatch的源码下载地址在这里,我选择阅读的版本是libdispatch-339.92.1

文章列表

本系列文章会分成以下几篇,对某篇感兴趣的可直接点击链接查看

GCD基础知识

在阅读GCD的源码之前,我们先了解一些相关知识,方便后面的理解。

DISPATCH_DECL
1
#define DISPATCH_DECL(name) typedef struct name##_s *name##_t

GCD中的变量大多使用了这个宏,比如DISPATCH_DECL(dispatch_queue)展开后是

1
typedef struct dispatch_queue_s *dispatch_queue_t;

它的意思是定义一个dispatch_queue_t类型的指针,指向了一个dispatch_queue_s类型的结构体。

fastpath vs slowpath
1
2
#define fastpath(x) ((typeof(x))__builtin_expect((long)(x), ~0l))
#define slowpath(x) ((typeof(x))__builtin_expect((long)(x), 0l))

__builtin_expect是编译器用来优化执行速度的函数,fastpath表示条件更可能成立,slowpath表示条件更不可能成立。我们在阅读源码的时候可以做忽略处理。

TSD

Thread Specific Data(TSD)是指线程私有数据。在多线程中,会用全局变量来实现多个函数间的数据共享,局部变量来实现内部的单独访问。TSD则是能够在同一个线程的不同函数中被访问,在不同线程时,相同的键值获取的数据随线程不同而不同。可以通过pthread的相关api来实现TSD:

1
2
3
4
5
6
//创建key
int pthread_key_create(pthread_key_t *, void (* _Nullable)(void *));
//get方法
void* _Nullable pthread_getspecific(pthread_key_t);
//set方法
int pthread_setspecific(pthread_key_t , const void * _Nullable);

常用数据结构

dispatch_object_s是GCD最基础的结构体,定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//GCD的基础结构体
struct dispatch_object_s {
DISPATCH_STRUCT_HEADER(object);
};

//os object头部宏定义
#define _OS_OBJECT_HEADER(isa, ref_cnt, xref_cnt) \
isa; \ //isa
int volatile ref_cnt; \ //引用计数
int volatile xref_cnt //外部引用计数,两者都为0时释放

//dispatch结构体头部
#define DISPATCH_STRUCT_HEADER(x) \
_OS_OBJECT_HEADER( \
const struct dispatch_##x##_vtable_s *do_vtable, \
do_ref_cnt, \
do_xref_cnt); \ //vtable结构体
struct dispatch_##x##_s *volatile do_next; \ //下一个do
struct dispatch_queue_s *do_targetq; \ //目标队列
void *do_ctxt; \ //上下文
void *do_finalizer; \ //销毁时调用函数
unsigned int do_suspend_cnt; //suspend暂停计数

dispatch_continuation_s结构体主要封装block和function,dispatch_async中的block最终都会封装成这个数据类型,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct dispatch_continuation_s {
DISPATCH_CONTINUATION_HEADER(continuation);
};

//continuation结构体头部
#define DISPATCH_CONTINUATION_HEADER(x) \
_OS_OBJECT_HEADER( \
const void *do_vtable, \
do_ref_cnt, \
do_xref_cnt); \ //_OS_OBJECT_HEADER定义
struct dispatch_##x##_s *volatile do_next; \ //下一个任务
dispatch_function_t dc_func; \ //执行内容
void *dc_ctxt; \ //上下文
void *dc_data; \ //相关数据
void *dc_other; //其他

dispatch_object_t是个union的联合体,可以用dispatch_object_t代表这个联合体里的所有数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef union {
struct _os_object_s *_os_obj;
struct dispatch_object_s *_do; //object结构体
struct dispatch_continuation_s *_dc; //任务,dispatch_aync的block会封装成这个数据结构
struct dispatch_queue_s *_dq; //队列
struct dispatch_queue_attr_s *_dqa; //队列属性
struct dispatch_group_s *_dg; //群组操作
struct dispatch_source_s *_ds; //source结构体
struct dispatch_mach_s *_dm;
struct dispatch_mach_msg_s *_dmsg;
struct dispatch_timer_aggregate_s *_dta;
struct dispatch_source_attr_s *_dsa; //source属性
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 __attribute__((__transparent_union__));

GCD中常见结构体(比如queue、semaphore等)的vtable字段中定义了很多函数回调,在后续代码分析中会经常看到,定义如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//dispatch vtable的头部
#define DISPATCH_VTABLE_HEADER(x) \
unsigned long const do_type; \ //类型
const char *const do_kind; \ //种类,比如:group/queue/semaphore
size_t (*const do_debug)(struct dispatch_##x##_s *, char *, size_t); \ //debug用
void (*const do_invoke)(struct dispatch_##x##_s *); \ //invoke回调
unsigned long (*const do_probe)(struct dispatch_##x##_s *); \ //probe回调
void (*const do_dispose)(struct dispatch_##x##_s *); //dispose回调,销毁时调用

//dx_xxx开头的宏定义,后续文章会用到,本质是调用vtable的do_xxx
#define dx_type(x) (x)->do_vtable->do_type
#define dx_metatype(x) ((x)->do_vtable->do_type & _DISPATCH_META_TYPE_MASK)
#define dx_kind(x) (x)->do_vtable->do_kind
#define dx_debug(x, y, z) (x)->do_vtable->do_debug((x), (y), (z))
#define dx_dispose(x) (x)->do_vtable->do_dispose(x)
#define dx_invoke(x) (x)->do_vtable->do_invoke(x)
#define dx_probe(x) (x)->do_vtable->do_probe(x)

dispatch_queue_s是队列的结构体,也是GCD中开发者接触最多的结构体了,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct dispatch_queue_s {
DISPATCH_STRUCT_HEADER(queue); //基础header
DISPATCH_QUEUE_HEADER; //队列头部,见下面的定义
DISPATCH_QUEUE_CACHELINE_PADDING; // for static queues only
};
//队列自己的头部定义
#define DISPATCH_QUEUE_HEADER \
uint32_t volatile dq_running; \ //队列运行的任务数量
struct dispatch_object_s *volatile dq_items_head; \ //链表头部节点
struct dispatch_object_s *volatile dq_items_tail; \ //链表尾部节点
dispatch_queue_t dq_specific_q; \ //specific队列
uint32_t dq_width; \ //队列并发数
unsigned int dq_is_thread_bound:1; \ //是否线程绑定
unsigned long dq_serialnum; \ //队列的序列号
const char *dq_label; \ //队列名
DISPATCH_INTROSPECTION_QUEUE_LIST;

队列的do_table中有很多函数指针,阅读queue的源码时会遇到dx_invoke或者dx_probe等函数,它们其实就是调用vtable中定义的函数。下面看一下相关定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//main-queue和普通queue的vtable定义
DISPATCH_VTABLE_INSTANCE(queue,
.do_type = DISPATCH_QUEUE_TYPE,
.do_kind = "queue",
.do_dispose = _dispatch_queue_dispose, //销毁时调用
.do_invoke = _dispatch_queue_invoke, //invoke函数
.do_probe = _dispatch_queue_probe, //probe函数
.do_debug = dispatch_queue_debug, //debug回调
);
//global-queue的vtable定义
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_root, queue,
.do_type = DISPATCH_QUEUE_ROOT_TYPE,
.do_kind = "global-queue",
.do_dispose = _dispatch_pthread_root_queue_dispose, //global-queue销毁时调用
.do_probe = _dispatch_root_queue_probe, //_dispatch_wakeup时会调用
.do_debug = dispatch_queue_debug, //debug回调
);

总结

上述是阅读GCD源码前的基础介绍,后续分析会详细讲解到。该系列参考学习了一些优秀博客的文章,链接见参考资料。

参考资料:

Tags: GCD
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章