libdispatch 结构类型

概述

GCD是一套强大的多线程方案,提供了多种任务队列来提高开发效率,通过阅读libdispatch的源码可以更好的理解GCD的工作流程,帮助我们设计更好的代码

结构类型

libdispatch使用宏定义实现了大量的模板结构类型,除此之外还使用了unionenum结合的方式实现动态参数类型的灵活性:

  • queue_type:队列类型,例如全局队列
  • source_type:资源统称,queue或者function都可以看做是一个资源
  • semaphore_type:信号类型,信号可以保证资源同时多线程竞争下的安全
  • continuation_type:派发任务会被封装成dispatch_continuation_t,然后被压入队列中

对于libdispatch的结构体类型来说,都存在DISPATCH_STRUCT_HEADER(x)类型的变量。通过##拼接变量名的方式对不同的类型生成动态的参数变量


联合体union保证了各变量享用同一个内存地址,这也意味着各变量是互斥的。通过联合体结构,libdispatch实现了类型强制转换的效果。另外,通过宏定义DISPATCH_DECL(name)来保证所有dispatch_xxx_t变量实际上是一个指向dispatch_xxx_s的指针:

一方面,union在使用时会分配一块足够大的内存(能够容纳任一一种类型),这意味着可以随时更换存储数据,同样可能造成数据的破坏;另一方面,它让C函数拥有了返回参数多样化的灵活性。但是要记住不同的数据类型在生成自身的do_vtable也有不同的表现:


queue为例,其结构类型为dispatch_queue_s,鉴于源码中使用了大量的宏定义增加属性,下面的结构是替换一部分宏定义后的结构:

一个线程任务队列有这么几个重要的属性:

  • do_vtable
    虚拟表,采用类似于C++的模板方式定义了一个结构体vtable,主要存储了结构类型相关的描述信息
  • do_targetq
    目标队列,GCD允许我们将某个任务队列指派到另外的任务队列中执行。当do_targetq不为空时,async的实现有会所不同
  • dq_width
    最大并发数,串行/主线程的这个值是1
  • do_ctxt
    线程上下文,用来存储线程池相关数据,比如用于线程挂起和唤醒的信号量、线程池尺寸等
  • do_suspend_cnt
    用作暂停标记,当大于等于2时表示任务为延时任务。在任务达到时会修改标记,然后唤醒队列调度任务

派发任务会被包装成dispatch_continuation_s结构体对象,同样

一个dispatch_continuation_s变量不总是只包装了单个任务,它被设置成复用机制,通过TSD的方式保证每个线程可以拥有一定数量的复用continuation,以此来减少不必要的内存分配开销。

  • dc_func
    承担执行任务的对象,宏定义_dispatch_client_callout最终会以dc_func(dc_ctxt)的方式回调
  • dc_ctxt
    存储了continuation对象的上下文数据,同样用于执行任务
  • do_vtable
    只有当do_vtable的值小于127时才表示变量是一个continuation,派发到主/串行队列的任务会被标记DISPATCH_OBJ_BARRIER_BIT屏障标记

libdispatch的结构对象都拥有自己的一张do_vtable虚拟表,同样采用模板式的方式生成,每一个具体的结构类型会生成一张对应类型的虚拟表,但是属性基本是统一的

虚拟表采用类型名拼接的方式生成不同类型的重载函数,由于libdispatch类型采用union结构,这两者结合极大的保证了执行的灵活性

  • do_type
    数据的具体类型,详见上文中的枚举值
  • do_kind
    数据的类型描述字符串,比如全局队列为global-queue
  • do_debug
    debug方法,用来获取调试时需要的变量信息字符串
  • do_probe
    用于检测传入对象中的一些值是否满足条件
  • do_invoke
    唤醒队列的方法,全局队列和主队列此项为NULL

disaptch_root_queue_context_s存储了线程运行过程中的上下文数据,默认情况下已经创建了多个全局的上下文对象

上下文对象在执行任务的过程中存储了线程信号信息以及可用线程池数量。

  • dgq_pending_dgq_pad
    当前版本源码中未使用
  • dgq_thread_mediator
    用于判断是否存在可用线程资源,如果存在返回1,否则后续将创建新线程执行任务
  • dgq_thread_pool_size
    线程池剩余可用数量,只有dgq_thread_mediator的查询返回0并且此项大于0时会尝试创建新线程用以执行任务

GCD创建了总共八个四种优先级的全局上下文对象,每个上下文最多可容纳255个线程数量


dispatch_semaphore_s是性能稍次于自旋锁的的信号量对象,用来保证资源使用的安全性。

相比其他的结构,信号量的内部要简洁的多,主要使用的就三个属性:

  • dsema_value
    当前信号值,当这个值小于0时无法访问加锁资源
  • dsema_orig
    初始化信号值,限制了同时访问资源的线程数量
  • dsema_sent_ksignals
    由于mach信号可能会被意外唤醒,通过原子操作来避免虚假信号

通过一张图表示上面提及的四种数据类型的关系:

线程私有变量

进程中的全局变量和静态变量是所有线程都能访问的共享变量,这意味着访问这样的数据需要昂贵的同步花销,Thread-specific-Data线程私有数据机制让每一个线程拥有私有的全局变量。libdispatch提供了四个pthread_key来存取这些数据,在初始化阶段进行初始化:

基于大概率使用的派发任务,libdispatch缓存了dispatch_continuation_s,采用复用模式的做法在每次async中尝试去获取空闲的continuation变量,通过两个函数存取数据:

1 收藏 评论

关于作者:林欣达

iOS码农一枚 个人主页 · 我的文章 · 1 ·     

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部