dispatch_async 在 libdispatch 是以 dispatch_async_f 来展示的。
在这个函数中,我们的 block 被封装成的 dispatch_continuation_t 结构,在这个函数还应用了懒加载的方式来使用 dispatch_continuation_t,首先它从线程的TLS(线程的私有存储,如果了解过 pthread 的同学们应该明白,线程都是有自己的私有存储的,这些私有存储不会被其他线程所使用,这里不清楚也无所谓,反正就是创建了一个 dispatch_continuation_t 数据)中提取出一个 dispatch_continuation_t 结构,如果线程中的TLS不存在 dispatch_continuation_t 结构的数据,则从堆中获取,这也就是 _dispatch_continuation_alloc_cacheonly() 和 _dispatch_async_f_slow() 函数的作用。
接下来,是将任务压进我们传进来的队列中,这里要详细讲述 _dispatch_queue_push 函数及其接下来的逻辑:
_dispatch_queue_push_list 中,判断传入的队列是否有内容,如果有,直接接在队列后面,如果没有,则将内容接在队列头,然后唤醒这个队列,注意我说的是内容,而不是任务,因为有时候使用这个函数压进去不一定是任务,也有可能是队列,没错,将队列压进队列中,其实不能说压,说接这个动词,更恰当一点。_dispatch_queue_push_list 函数用得还是 inline 函数,说明这个函数会调用很频繁,inline 通常用在内核中,效率很高,但生成的二进制文件会变大,我个人猜想,这个函数之所以等级这么高,是因为我们应用层开发者写的代码就是用这个函数压进我们需要运行的队列中的。
上面说到如果队列中没有任务,则会使用 _dispatch_wakeup() 函数唤醒队列,让我们来看一下是怎么唤醒队列的:
在上图中,我们要根据传入不同的队列来进行分析:
1.传入主队列,会进入到 _dispatch_queue_wakeup_main() 函数中,这里不加以分析。
2.传入全局队列,会进入到 全局队列的 dx_probe 函数中,也就是我上文说的 _dispatch_queue_wakeup_global 函数,等下会重点分析这个函数。
3.传入用户自定义的队列,首先用户自定义的任务是没有 dx_probe 函数的,所以会继续往下走,在这里可以看到,它会被压进它的目标队列,也就是全局队列中。然后进入我刚才说的 2 的传入全局队列模式中且这个队列接到了全局队列中。
重点分析一下 dispatch_queue_wakeup_global:
这个函数前面是对传入的全局队列的检测,初始化和配置环境,我的讲解将从它发送了一个信号量开始。
这个函数里面发送了一个信号量,这是一种线程保活的方法,这里等我讲到这个信号量等待函数再来讲解。
继续往下走检测线程池的大小,如果线程池还有,则线程池减1。接下来就是使用 pthread 库创建一个线程,线程的入口是 _dispatch_worker_thread 。接下来的分析我们就进入了另一个线程了:
这是新线程的入口,前面主要是设置新线程的信号掩码,真正的任务调度在 _dispatch_worker_thread2 里面,而我们也可以看到,这个任务调度结束后,这个线程在等待一个信号量,而等待的信号量就是前面 dispatch_queue_wakeup_global 里面的信号量,为什么要这样做?这样做的原因是不要频繁开启新线程,如果有一个新线程完成所有任务了,这个线程就要结束了,但这里并不是这样,而是等待一个信号量,大约等待65秒,如果65秒内接收到新的信号量(表示有新的任务),这个线程就会去继续执行加进来的任务,而不是重新开启新线程,65秒后没接收到信号量,则退出这个线程,销毁这个线程。
接下来看一下任务怎么调度的:
这个函数里面进行任务的调度,主要是两个函数,一个是 _dispatch_queue_concurrent_drain_one ,用来取出队列的一个内容,另一个是 _dispatch_continuation_pop 函数,用来对取出的内容进行处理(如果是任务,则执行任务)。首先来看一下 _dispatch_queue_concurrent_drain_one 函数:
这个函数首先检测形参队列是不是空的,如果空的,就返回NULL。如果这个队列(其实是全局队列的一个)有内容,就分成两种情况:
1.这个队列只有一个内容,就返回这个内容;
2.这个队列不止只有一个内容,就调用 _dispatch_queue_wakeup_global 函数(其实就是创建一个新的线程,继续调度这个队列里面的内容);
这样,就可以从 _dispatch_queue_concurrent_drain_one 函数中获得了队列的一个内容,然后 _dispatch_continuation_pop 这个函数进行处理这个内容:
这个函数首先检测传进来的内容是不是队列,如果是队列,就进入 _dispatch_queue_invoke 处理队列,如果不是,那这个形参就是任务封装的 dispatch_continuation_t 结构体,直接执行任务。
这里就不截图源码来说明 _dispatch_queue_invoke 函数了,我这里简单的说明一下它的逻辑,_dispatch_queue_invoke 函数首先会检测出这个队列是不是串行队列,如果是串行队列,就一个一个取出任务执行,如果是并发队列,就取出一个任务执行,然后将这个并发队列的其余部分压进 rootqueue 里面,这样就使并发队列的任务并发执行了。