GCD源码的分析

作者: 酸菜Amour | 来源:发表于2016-08-29 14:25 被阅读2963次

    最近看了苹果 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 异步执行和同步执行等的实现机制。

    相关文章

      网友评论

      • 我本善良:感谢楼主分享,有个问题是阅读源码的时候看到这个文件的父目录是libdispatch/,下面有很多子文件夹,他们的区别是不同版本的更新,还是每个文件夹代表不同模块的实现,对比了下没发现规律。楼主阅读GCD源码的时候,是怎么定位从哪个文件夹开始的?求点明方向:blush:
      • 人仙儿a:楼主, libdispatch-187.10.tar.gz , libdispatch-228.18.tar.gz 有什么区别呢?187跟228有啥区别呢?哪个是最新的呢?

      本文标题:GCD源码的分析

      本文链接:https://www.haomeiwen.com/subject/zmfxettx.html