最近看了苹果 libdispatch 的源码,也就是 GCD 的源码,对于 GCD 的运作方式有一定了解和自己的见解。我看的 libdispatch 并不是苹果最新版本的源码,但按道理来说 GCD 的运作逻辑应该不会很大的改变,细节方面是有所改变的,但并不影响我们对 GCD 的运作机制的了解。这里是 GCD 的源码:libdispatch源码。
首先来说一下 GCD 中用到的数据类型,在 base.h 文件中有所提到:
GCD 数据结构上图展示了 GCD 的所需数据类型的结构,我讲述一下这些数据类型干什么用的:
_do: 这个是 dispatch_object_s * 结构体,这个是 GCD 的基类,GCD 数据结构都是由这个结构体搭建起来的。
_dc: 这个是任务类型,通常 dispatch_async 内的 block 最终都会封装成这个数据类型。
_dq: 这个是任务队列,我们创建的对列都是这个类型的,不管是串行队列还是并发队列。
_dqa: 这个是任务队列的属性,任务队列的属性里面包含了任务队列里面的一些操作函数,可以表明这个任务队列是串行还是并发队列。
_ds: 这个是 GCD 的 sourece ,可以监测内核事件,文件读写事件和 socket 通信事件等。
_dsa: sourece 的属性。
_dsema: 信号量,如果了解过 pthread 都知道,信号量可以用来调度线程。
dispatch_object_t 是一个联合体,所以当用 dispatch_object_t 可以代表这个结合体内的所有数据类型。
接下来详细说明一下 dispatch_object_s 内的结构是怎么样的(大家也可以阅读这篇文章:变态的libDispatch结构分析-object结构 - 爱悠闲):
从上两图可以清晰的了解到 dispatch_object_s 的结构,这里讲解一下,这个结构体内的一些数据的作用:
dispatch_object_s 最前面有一个 vtable 的结构体,这个结构体内包含了这个 dispatch_object_s 的操作函数,vtable内的数据结构的解析:
do_type:这个 dispatch_object_s 的类型。
do_kined:说明这个 dispatch_object_s 。
do_debug:debug 方法。
do_invoke: 唤醒队列的方法。
do_dispose:销毁队列的方法,通常内部会调用 这个对象的 finalizer 函数。
do_probe:这个方法很重要,用户创建的队列这个方法是空的,但 rootqueue 内的这个有一个 _dispatch_queue_wakeup_global 函数,这是个很重要的函数。
上面大体是 vtable 内的数据,其余的是:
do_next:链表的 next 。
do_ref_cnt:引用计数。
do_ref_xcnt:外部引用计数。(这两个是用来内存管理的,这里探究 GCD 的机制,我也就没多搞清楚这两个是怎么用的)
do_suspend_cnt:suspend计数,用作暂停标志,比如延时处理的任务,设置该引用计数之后;在任务到时后,计时器处理将会将该标志位修改,然后唤醒队列调度。
dispatch_queue_s:目标队列,就是当前这个struct x在哪个队列运行。
do_ctext:上下文,我们要传递的参数。
下面说一下,了解 GCD 所需要的知识:
在程序运行的时候,GCD 会初始化 六个 rootqueue 和 一个 mainqueue,这六个 rootqueue 有低中高三种优先级,这些 rootqueue 主要是用来调度任务,我们自己创建的队列其实并不能调配任务,因为我们创建的队列的 do_probe 都是空的,我自己创建的队列都是链接在 rootqueue 下的,利用 rootqueue 来调配任务,所以我也建议不要使用 GCD 创建队列,这样会使内部处理更加复杂,这个以后详细说明。 mianqueue 是要绑定在 UI 线程的,用来更新界面的,mainqueue 是一种串行队列。 mianqueue 在用户层彰显的就是 dispatch_get_main_queue(),而 rootqueue 彰显的是 dispatch_get_global_queue 。下面讲解一下 dispatch_queue_s 的结构是怎么样的:
上图可以看出,dispatch_queue_s 和 dispatch_object_s 差别在于 dispatch_queue_s 多了DISPATCH_QUEUE_HEADER 和 dq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE],我们从中也可以看出,我们队列起名要少于64个字符,DISPATCH_QUEUE_HEADER 内的内容有:
dq_running: 队列正在运行的任务数。
dq_width: 队列的宽度(串行队列为1,并发队列应该大于1)。
dq_item_tail: 指向这个队列的尾节点。
dq_item_head: 指向这个队列的头节点。
dq_serialnum: 不知道干什么的。。。。
dq_finalizer_ctxt: 结束函数的的上下文(参数)。
dq_finalizer_func: 结束函数。
可以看一下一开始初始化的6个 rootqueue 和 mainqueue 的内容:
上图展示 6个 rootqueue 和 mianqueue 的具体内容,主要讲一下里面重要的,在 rootqueue 里面 重要的是 do_vtable 内的do_provbe 的函数是什么,截图一下:
可以看出 rootsueu 的函数是 _dispatch_queue_wakeup_global,这个很重要。
在 libdispatch 里面有一些原子操作函数,这些函数都是由汇编写成的,效率很高,而且不会被其他线程干扰,这里说明一下一些原子函数的作用:
dispatch_atomic_cmpxchg(A, B, C) : 将 A 和 B对比,相等,则将 C 赋值给 A,返回 YES,否则返回 NO。
dispatch_atomic_xchg(A, C): 将 C 赋值给 A , 返回赋值前的 A 。
dispatch_atomic_inc(A): A 自增1。
dispatch_atomic_dec(A): A 自减1。
dispatch_atomic_add(A, B): A = A + B 。
Adispatch_atomic_sub(A, B): A = A - B 。
dispatch_atomic_or(A, B): A = A | B 。
dispatch_atomic_and(A, B): A = A & B 。
在 GCD 内有一宏定义: _dispatch_hardware_pause(),这个宏定义其实是 asm("pause"),在 __asm__("pause")用法_Linux_IThao123 - IT行业第一站 说明了这个用法的好处,简单说一下,在 x86 架构的 CPU 中,在循环用这个汇编指令,可以保证循环不会被退出且可降低功耗,苹果手机用的是 ARM 指令,用这个汇编指令应该作用不大。
下面的文章将详细分析 GCD 异步执行和同步执行等的实现机制。
网友评论