多线程之GCD源码分析

472 阅读2分钟

准备

libdispatch源码

主队列分析

dispatch_get_main_queue(),主队列在main函数之前就会创建。如下图,在main函数下断点,发现已经有了主队列

主队列.png 打开libdispatch源码工程,搜索dispatch_get_main_queue,

dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
  • DISPATCH_GLOBAL_OBJECT实际上一个宏,传入了一个type,和object参数。可以理解为传入这两个参数后,封装得到一个队列 dispatch_queue_main_t:类型 _dispatch_main_q:队列对象。
  • 全局搜索_dispatch_main_q =

队列结构体.png 实际上是一个结构体,并且继承DISPATCH_GLOBAL_OBJECT_HEADER,类似objc_class继承objc_object

串行队列和并行队列

我们通过dispatch_queue_create方法创建队列,查看源码:会发现调用_dispatch_lane_create_with_target,代码差不多100多行,抓住重点,主要看函数的返回值。

dispatch_lane_t dq = _dispatch_object_alloc(vtable,sizeof(struct dispatch_lane_s));
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
  • _dispatch_object_alloc:开辟内存空间,调用_os_object_alloc_realized
_os_object_alloc_realized(**const** **void** *cls, size_t size)

{

_os_object_t obj;

dispatch_assert(size >= **sizeof**(**struct** _os_object_s));

while (unlikely(!(obj = calloc(1u, size)))) {

_dispatch_temporary_resource_shortage();

}

obj->os_obj_isa = cls;

return* obj;

}

调用calloc开辟空间,同时objisa指向vtable

  • _dispatch_queue_init:这里面最主要做了队列的初始化,让后通过DQF_WIDTH(width),来区分是串行队列和并发队列。width = DISPATCH_QUEUE_WIDTH_MAX这个宏,表示并非对了,width = 1,表示串行队列。 其中dqai:是dispatch_queue_attr_info_t,实际上就是对优先级做了面向对象的分装。

GCD底层源码继承链

无论是什么队列,最后我们都是以dispatch_queue_t来接受,顺着dispatch_queue_t去查看源码:

截屏2021-08-08 下午4.53.26.png 会通过DISPATCH_DECL宏定义: #define DISPATCH_DECL(name) \

typedef struct name##_s : public dispatch_object_s {} *name##_t

dispatch_queue放进去,其中##是连接符号,编译时候会去掉。

  • typedef struct dispatch_queue_s : public dispatch_object_s {} *dispatch_queue_t 所以有这样一个继承链接dispatch_queue_t->dispatch_queue_s->dispatch_object_s
  • 实际上深入了解dispatch_object_s底层还会继承dispatch_object_tdispatch_object_t底层实际上是一个联合体:

截屏2021-08-08 下午5.57.48.png

GCD任务调用流程

同步任务

dispatch_sync(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"123")
});

同步函数的block是什么时候调用的,下面我们通过源码分析一下

同步.png 只要通过一条思路,根据work参数的传递路线,就可以查到block最终什么时候调用,也就是任务的执行。

dispatch_sync->_dispatch_sync_f->_dispatch_sync_f_inline

_dispatch_sync_f_inline.png _dispatch_sync_f_inline:发现很多地方都有传入block参数,我们可以新建工程,通过符号断点,一个个去试。dq_width 为1表示串行队列,所以不会走。 下_dispatch_sync_f_slow符号断点,进入了此方法。

_dispatch_sync_f_slow.png 继续符号跟踪_dispatch_sync_function_invoke

_dispatch_sync_function_invoke_inline.png 发现调用_dispatch_client_callout,这个方法就是block的调用。 同时通过堆栈,也可以证实我们的分析:

bt.png

异步任务

dispatch_async源码:

截屏2021-08-09 上午10.54.50.png 首先通过_dispatch_continuation_init将队列和block封装在一起。然后作为参数传入_dispatch_continuation_async方法。

_dispatch_continuation_async.png 然后全局搜索dx_push方法

定义.png vtable实际上就是通过队列封装的类,然后搜索dq_push,发现不同的队列,通过不同的方法实现

队列实现.png 下面我们以全局并发队列开始继续跟踪:_dispatch_root_queue_push,会调用_dispatch_root_queue_push_inline

_dispatch_root_queue_push_inline.png

继续跟踪:_dispatch_root_queue_push_inline->_dispatch_root_queue_poke_slow 发现会有个一个_dispatch_root_queues_init,这个里面会有一个单列_dispatch_root_queues_init_once,进行初始化

  • 线程池初始化:_dispatch_root_queue_init_pthread_pool
  • 工作队列的配置:pthread_workqueue_config
  • 执行函数的设置:cfg.workq_cb = _dispatch_worker_thread2 通过bt打印也证实了这一点:

截屏2021-08-09 下午2.29.11.png _dispatch_worker_thread2传给系统方法,最后通过系统cpu来调度。