最近在浏览react native代码的时候发现有提到main queue和main thread的区别,很早就有阅读gcd源码的冲动,这回总算找到机会了。
阅读源码之前先给个结论:main thread 和 main queue是两个不同的东西。
main queue is bound to main thread.
main thread is not bound to main queue.

just remember this png!!
可能大家对<code>__builtin_expect</code>比较熟悉,这是编译器可以用来优化执行速度的函数。
程序员在写if条件的时候,可能知道比较的值更可能是哪种情况,因此就可以使用<code>fastpath</code>
或者<code>slowpath</code>来告诉编译器,让编译来帮忙优化。
<code>fastpath</code>表示条件更可能成立
<code>slowpath</code>表示条件更不可能成立
所以简单的来说,当我们遇到这个东西的时候,直接忽略,并不会影响我们对代码的理解。
gcd支持function和block的执行,相应的其提供了两种方法来支持function和block的入队,简单的举个例子。所以当我们看到不带f和带f的同名函数,默认他们干的是同一回事。
block的执行底层调用的是function的执行。
libdispatch为了在保证性能的情况下,尽量增加代码的可读性,大量的使用了宏。
因此了解结构体之前,我们需要先知道几个宏定义,方便后续分析代码.
因为本文只关心queue的实现,所以暂时省略了其他结构体,有兴趣的童鞋可以直接下源码看。
系统基类
该结构体可以看成gcd的基类。
通过上面的结构体定义可以发现,<code>dispatch_object_t</code>可以是union结构体中任何一种类型。
该结构体主要用来封装block和function
队列结构体,可能是gcd最重要的结构体了。
队列属性结构体。
gcd提供了非常多的功能来简化针对多核设备的代码编写,这里我们慢慢添加对常用api的源码剖析。
当我们获取global queue来使用的时候,其实质上通过<code>_dispatch_get_root_queue</code>来获取的非overcommit的预先生成的队列的。
<code>_dispatch_get_root_queue</code>是从结构体<code>_dispatch_root_queues</code>中获取相应的优先级的队列。<code>_dispatch_root_queues</code>区分是否overcommit,定义了4中优先级的队列,他们分别是(参考前文的图)。最后1bit是1的代表overcommit。overcommit用来控制线程数能不能超越物理内核数,显然通过该接口获得的队列不会给系统创建过多的队列。
最后提及一下,<code>_dispatch_root_queues</code>对应的thread实现在<code>_dispatch_root_queue_contexts</code>中,每一个context都是一个线程池,每个线程池的最大线程数限制是255。
可以发现当我们通过<code>dispatch_get_current_queue</code>来获取当前运行的队列的时候,我们是通过tsd(thread specific data)来确定到底当前是运行在那个queue上的,每当我们切换queue的时候,都是通过<code>_dispatch_thread_setspecific</code>来设置当前queue。
这里主要涉及到tsd,也有叫tld的,是个比较有意思的技术。
当我们调用<code>dispatch_queue_create</code>进行queue的创建的时候,其会首先调用<code>_dispatch_queue_init</code>初始化一个queue,该queue默认是default的优先级(还记得前文的图么?),并且dq_width是1,也就是串行队列,并且序列号加1。
如果是并发队列的话,会将dq_width改成uint32_max,并且将目标queue设置成非overcommit的。overcommit如果被设置成true,那就意味着可以创建超过物理核数目的线程数。因此可以发现,自定义并发队列线程数目是不会超过物理内核数的,而串行队列一般是没有这个限制的。
前面说到序列号加1了,那序列号是干什么的呢?在源码中有这样一段注释。
可以发现,序列号是1的时候表示main queue,2的时候表示manager queue,3没有使用,4到11表示global queue,再往后就是用户自定义queue了。
那接下来我们看看序列1和序列2的queue是怎么定义的。
值得说明的是,main queue的目标queue也只是一个优先级为default的overcommit queue,其背后也是普通的线程池,nothing special。
该队列是用来管理gcd内部的任务的,比如对于各类source的管理等。
无论走哪个分支,深入查看之后,可以发现,其最终走的都是<code>dispatch_sync_f</code>,通过<code>_dispatch_block_copy</code>或者<code>block_basic</code>来实现block到function的转换。
如果dq_width是1的话,也就是dq是串行队列的话,必须要等待前面的任务执行完成之后才能执行该任务,因此会调用<code>dispatch_barrier_sync_f</code>。barrier的实现是依靠信号量机制来保证的。
如果当前队列中有对象或者当前队列处于暂停状态或者当前队列没有运行任何任务的时候,就走<code>_dispatch_barrier_sync_f_slow</code>慢通道,否则的话就直接调用<code>_dispatch_barrier_sync_f_invoke</code>。可以发现<code>dispatch_sync</code>一般来说都是在当前线程执行的,不进行线程的切换,这一点还是要特别注意的。只有走<code>slow</code>的时候,才会做线程切换。下面就看下走<code>slow</code>路径是什么样的。
代码比较长,截取开头一部分,我们可以发现,其通过信号量来同步任务的执行,需要切换线程。
简单看下<code>_dispatch_function_invoke</code>,主要关注queue和thread的关系并非一一对应的。queue是基于thread的,我们在使用queue的时候,就不应该再操作thread,防止出现意外的情况。
当我们切换线程的时候,我们会先更改tsd,执行block,然后再将之前的dq设置回去。
这个就比较显而易见了,直接就把block转成function,然后掉xxx_f函数了。
看过前面几个例子之后就比较清晰了,如果dq是串行队列的话,就走<code>dispatch_barrier_async_f</code>,否则的话就需要创建一个<code>dispatch_continuation</code>对象来存放function,然后塞到队列后面。其实这两个路径都是干了一件事情,就是创建dc对象,然后塞到队列后面,唯一的区别是
其实到这里我们可以知道,gcd队列的阻塞等待就是通过<code>dispatch_obj_barrier_bit</code>这个bit标识来实现的,全部的标识有如下几种:
理解这个对于阅读源码作用还是比较大的。
接下去是比较重要的部分,也是隐藏的比较深的部分,我们现在的确把dc给放进队列里面了,那什么时候改dc才会被执行呢?正常的实现应该是上一个任务完成之后主动触发下一个任务。我们看下几个函数
可以发现无论是同步的任务执行,还是异步的任务执行,其最终都会调用<code>_dispatch_wakeup</code>,该函数的作用就是触发下一个任务。
一开始看半天没看出来咋整的,有个非常不起眼的宏<code>dx_probe</code>,他的定义是<code>(x)->do_vtable->do_probe(x)</code>,搜了一下<code>do_probe</code>,可以发现,在init.c中有相关的定义,我们讨论到现在的基本都是root_queue,所以只看root_queue。
<code>_dispatch_queue_wakeup_global_slow</code>的代码这里就不贴了,在这个函数中会发送生产者消息让下一个任务执行,同时也会在必要的时候创建线程。这里是比较隐晦的,但原理和我们猜的也差不多。
看这个代码断断续续也看了几天,终于看出了一点端倪。感谢网友博客提供的关键信息说明,少走了很多弯路。特此记录,以备后用。
<a href="https://bestswifter.com/deep-gcd/">深入理解gcd</a>
<a href="http://www.jianshu.com/p/a639e2159aa1">gcd源码的分析</a>
<a href="http://libdispatch.macosforge.org/trac/wiki/tutorial">libdispatch</a>
<a href="http://blog.krzyzanowskim.com/2016/06/03/queues-are-not-bound-to-any-specific-thread/">queues are not bound to any specific thread</a>