GCD 是 iOS 中对多线程能力的一种封装,全名称为 Grand Central Dispatch,一般情况下,我们只需要了解 GCD 的基本使用,便可以在 iOS 中进行简单高效的多线程开发。
iOS 中的多线程
与 GCD 的功能类似的,还有两个,分别是 NSThread 和 NSOperation,这里简单做下区分。
GCD
GCD 中我们一般操作的是分发队列 dispatch queue,虽然很多情况下我们模糊了队列与线程之间的区别,但是本质上,队列只是一个用于维护任务进行 FIFO 的数据结构,它内部是要维护一个线程池来实际地去执行任务的。
至于具体在什么线程上、何时执行任务,要根据 dispatch queue 和执行的方法具体去看。
NSThread
NSThread 操作的就是一个真实存在的线程,它也提供了一些线程操作的方法,如创建线程、线程休眠、退出等。
NSOperation
NSOperation 一般是要与 NSOperationQueue 一起使用才有效果,而 NSOperationQueue 一般说是对 dispatch queue 的封装,不同于 GCD 那种 c 语言风格的 api,NSOperationQueue 是正经的 ObjC 对象,NSOperation 也是,他们在 GCD 的基础上增加了很多对象操作的方法,比如任务的取消、任务间的互相依赖等。
但是相对的,就执行效率而言,要比 GCD 要低一些。
类型
功能类型
从 dispatch queue 的功能类型来说, 它有两种,分别是串行队列和并行队列,简单来说,
- 给到串行队列的所有任务,都会按照先后顺序依次执行,但是具体在哪个线程执行并不确定
- 给到并行队列的所有任务,一般会直接执行,但执行在哪个线程也是不能确定的
功用类型
从功用类型来说,可以将 dispatch queue 划分为系统创建的 global queue,main queue 和用户自己创建的 queue,它们最根本的区别在于,系统创建的 queue 是真实可以执行任务的,而用户创建的 queue 只起到一个控制任务什么时候可以提交给系统创建的的任务的作用,他们之间,就是依赖 target queue。
- global queue 是系统内置的一些不同优先级的并行队列,一共五种
- mian queue 是主队列,它有且只有一个运行线程,就是主线程
我们自己创建的 dispatch queue,不论是并行队列还是串行队列,它们默认的 target queue 都是 QOS_CLASS_DEFAULT 类型的 global queue,也就是说最终它们都是要执行在 global queue 队列上,但是因为这是一个并行队列,所以从使用上来说,并不能感知到什么。
功能
dispatch queue 有两个最主要的功能就是执行任务,分别是同步执行 dispatch_sync 和异步执行 dispatch_async。
- 一般来说,所有使用 dispatch_sync 执行的任务,线程会阻塞,等待任务执行完毕才会继续执行下一个命令,即,同步执行的任务只是使用了队列的 FIFO 功能,其执行的线程还是当前线程
- 所有使用 dispatch_async 执行的命令,都不会阻塞当前线程,系统会为其选择一个合适的线程执行任务
任务与线程
正因为 dispatch queue 只是对多线程的一种封装,所以就会导致有些时候我们将一个任务交给队列执行之后,都不清楚它到底是运行在哪个线程,在何时执行的。先说结论:
| 并行队列 | 串行队列 | |
|---|---|---|
| 当前队列 dispatch async | 开启新线程 | 不开启线程,当前任务执行完后执行 |
| 当前队列 dispatch sync | 不开启线程,当前队列执行 | 死锁 |
| 其他队列 dispatch async | 开启新线程 | 开启新线程 |
| 其他队列 dispatch sync | 不开启线程,当前队列执行 | 不开启线程,当前队列执行 |
如上所示,就是当不同类型的队列遇到不同类型的执行方式后产生的反应。总结起来就是下面几点:
- dispatch sync 在当前线程执行,但需要依赖按照队列决定什么时候才能执行
- dispatch async 执行线程由队列决定,一般是立刻就能执行
- 串行队列最多开启一个线程,所有任务都要依次执行
- 并行队列可开启多个线程,任务一般是给过来就可以执行
代码分析
单从理论分析显得干瘪,代码才能说明一切,下面就从 libdispatch-1271.40.12 这个源代码中,简单看一下 dispath queue 相关的实现。
创建队列
先看队列的创建,
// 创建队列
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr);
// 设置 dispatch_queue_attr_t qos
dispatch_queue_attr_t
dispatch_queue_attr_make_with_qos_class(dispatch_queue_attr_t _Nullable attr,
dispatch_qos_class_t qos_class, int relative_priority);
dispatch_queue_attr_t 中定义了很多队列的属性,比如是否是一个并行队列,它的 qos(quality of service,决定它的 target queue)是什么等等。一般情况下,系统已经提供好了两个几个默认的 dispatch_queue_attr_t,比如 DISPATCH_QUEUE_SERIAL,比如 DISPATCH_QUEUE_CONCURRENT,至于 qos,则可以通过函数 dispatch_queue_attr_make_with_qos_class 设置,qos 有几种不同的类型:
__QOS_ENUM(qos_class, unsigned int,
QOS_CLASS_USER_INTERACTIVE
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x21,
QOS_CLASS_USER_INITIATED
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x19,
QOS_CLASS_DEFAULT
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x15,
QOS_CLASS_UTILITY
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x11,
QOS_CLASS_BACKGROUND
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x09,
QOS_CLASS_UNSPECIFIED
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x00,
);
可以从名字大致区分下,跟优先级的理解应该类似,其中优先级最高的就是用户交互类相关的,这也对应着几种不同的 global queue,所以联系起来,就是创建队列时用的 qos,就决定了它的 target queue 是哪个一个 global queue,上面也说过,用户创建的队列是不会真正执行任务的,所有的任务最终都是给到 target queue 执行。
总的来说,创建 dispatch queue 的过程,就是根据 dispatch_queue_attr_t 绑定资源的过程。
同步执行
同步执行一般指 dispatch_sync 开头的一系列函数,比如
void
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void
dispatch_barrier_sync(dispatch_queue_t queue,
DISPATCH_NOESCAPE dispatch_block_t block);
首先要说的一点是,所有提交给队列执行的 block,都会先被包装成 function 再往下,通过函数 _dispatch_Block_invoke,
#define _dispatch_Block_invoke(bb) \
((dispatch_function_t)((struct Block_layout *)bb)->invoke)
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
从它的声明看,就是将 Block_layout 的 invoke 函数取了出来,Block_layout 可以理解为 block 在 c 中的内存布局吧,它有 isa 指针,和 invoke 函数。
然后在后面的处理中,并行队列和串行队列分别做了不同的处理,对于串行队列来说,它的每一个 async/sync 都是要一个个执行的,所以没提交一个任务,可能都要有一个等待、唤醒的过程,而这也正好与 barrier_sync 的功能一致;对于并行队列来说,提交给它的 sync 任务会立刻被执行,可以从下面看到:
static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
if (likely(dq->dq_width == 1)) {
// 串行队列走这里
return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
}
if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
}
dispatch_lane_t dl = upcast(dq)._dl;
// Global concurrent queues and queues bound to non-dispatch threads
// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {
// 无法开始执行,比如有其他 barrier 类的 task 正在执行
// 就会进入 slow 模式,等待执行
return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);
}
if (unlikely(dq->do_targetq->do_targetq)) {
// 如果当前的 dispatch_queue 的 target 还不是 global queue,那就递归地
// 将任务一步步向 target queue 提交,直到提交给 global queue
return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);
}
_dispatch_introspection_sync_begin(dl);
// 走到这里直接就执行了
_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));
}
下面就看串行队列中的后续,
static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
dispatch_tid tid = _dispatch_tid_self();
if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
}
dispatch_lane_t dl = upcast(dq)._dl;
// The more correct thing to do would be to merge the qos of the thread
// that just acquired the barrier lock into the queue state.
//
// However this is too expensive for the fast path, so skip doing it.
// The chosen tradeoff is that if an enqueue on a lower priority thread
// contends with this fast path, this thread may receive a useless override.
//
// Global concurrent queues and queues bound to non-dispatch threads
// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
// 先判断是否有任务正在执行,进行等待,直到上一个任务执行完
if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {
return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,
DC_FLAG_BARRIER | dc_flags);
}
if (unlikely(dl->do_targetq->do_targetq)) {
// 同样的,如果 targetr queue 不是 global queue,先递归找到 global queue
return _dispatch_sync_recurse(dl, ctxt, func,
DC_FLAG_BARRIER | dc_flags);
}
_dispatch_introspection_sync_begin(dl);
// 执行
_dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));
}
从代码中可以看出,无论是并行队列还是串行队列,它们都会有一个先判断的过程,但是区别还是有的:
- 在并行队列中,是判断是否有 barrier 类型的任务在执行,并将正在执行的任务数量记录下,对应函数为 _dispatch_queue_try_reserve_sync_width
- 在串行队列中,是判断是否有任务在执行,对应函数 _dispatch_queue_try_acquire_barrier_sync
如果以上两个判断都走过了没有问题,就会直接在当前线程执行,而如果判断失败,就会调用 _dispatch_sync_f_slow ,走等待、唤醒、执行这样一个流程。它的实现如下:
static void
_dispatch_sync_f_slow(dispatch_queue_class_t top_dqu, void *ctxt,
dispatch_function_t func, uintptr_t top_dc_flags,
dispatch_queue_class_t dqu, uintptr_t dc_flags)
{
dispatch_queue_t top_dq = top_dqu._dq;
dispatch_queue_t dq = dqu._dq;
// 如果队列是系统创建的 global queue 或 main queue,直接执行,
// 不考虑是否需要等待
if (unlikely(!dq->do_targetq)) {
return _dispatch_sync_function_invoke(dq, ctxt, func);
}
// 执行等待流程
pthread_priority_t pp = _dispatch_get_priority();
struct dispatch_sync_context_s dsc = {
.dc_flags = DC_FLAG_SYNC_WAITER | dc_flags,
.dc_func = _dispatch_async_and_wait_invoke,
.dc_ctxt = &dsc,
.dc_other = top_dq,
.dc_priority = pp | _PTHREAD_PRIORITY_ENFORCE_FLAG,
.dc_voucher = _voucher_get(),
.dsc_func = func,
.dsc_ctxt = ctxt,
.dsc_waiter = _dispatch_tid_self(),
};
_dispatch_trace_item_push(top_dq, &dsc);
// 等待任务执行的函数
__DISPATCH_WAIT_FOR_QUEUE__(&dsc, dq);
if (dsc.dsc_func == NULL) {
// dsc_func being cleared means that the block ran on another thread ie.
// case (2) as listed in _dispatch_async_and_wait_f_slow.
dispatch_queue_t stop_dq = dsc.dc_other;
return _dispatch_sync_complete_recurse(top_dq, stop_dq, top_dc_flags);
}
_dispatch_introspection_sync_begin(top_dq);
_dispatch_trace_item_pop(top_dq, &dsc);
_dispatch_sync_invoke_and_complete_recurse(top_dq, ctxt, func,top_dc_flags
DISPATCH_TRACE_ARG(&dsc));
}
主要的就是将现有的任务封装成了 dispatch_sync_context_s(可以理解为一个新的任务),然后将新的任务通过 async 的方式提交到队列上(通过 __DISPATCH_WAIT_FOR_QUEUE__,这个函数可以理解为与 dispatch_async_and_wait 类似),此时提交给队列的任务就变成了 dispatch_sync_context_s,它的执行函数则是 _dispatch_async_and_wait_invoke,参数为 &dsc:
static void
_dispatch_async_and_wait_invoke(void *ctxt)
{
dispatch_sync_context_t dsc = ctxt;
dispatch_queue_t top_dq = dsc->dc_other;
dispatch_invoke_flags_t iflags;
// the block runs on the thread the queue is bound to and not
// on the calling thread, but we want to see the calling thread
// dispatch thread frames, so we fake the link, and then undo it
iflags = dsc->dsc_autorelease * DISPATCH_INVOKE_AUTORELEASE_ALWAYS;
dispatch_invoke_with_autoreleasepool(iflags, {
dispatch_thread_frame_s dtf;
_dispatch_introspection_sync_begin(top_dq);
_dispatch_thread_frame_push_and_rebase(&dtf, top_dq, &dsc->dsc_dtf);
_dispatch_client_callout(dsc->dsc_ctxt, dsc->dsc_func);
_dispatch_thread_frame_pop(&dtf);
});
// communicate back to _dispatch_async_and_wait_f_slow and
// _dispatch_sync_f_slow on which queue the work item was invoked
// so that the *_complete_recurse() call stops unlocking when it reaches it
dsc->dc_other = _dispatch_queue_get_current();
dsc->dsc_func = NULL;
if (dsc->dc_data == DISPATCH_WLH_ANON) {
_dispatch_thread_event_signal(&dsc->dsc_event); // release
} else {
_dispatch_event_loop_cancel_waiter(dsc);
}
}
这个函数主要的还是通过 _dispatch_client_callout 调用原先的任务函数,dsc_ctxt 和 dsc_func 从上面可以了解到,就是 _dispatch_sync_f_slow 中的 ctxt 和 func,到这里这个同步任务算是执行完了。
总结一下,可以看出 dispatch_sync 执行的 block,有两种执行方式,
- 如果当前不需要等待,就在当前线程直接执行
- 如果需要等待其他任务执行完,就会通过类似 dispatcch_async_and_wait 的方式执行任务,此时不一定还是在当前线程执行(不过一般不会把 dispatch_sync 提交到当前代码执行的队列中,因为这会导致死锁)
第二种方式中实际上属于是异步执行了,下面就看下异步执行的实现。
异步执行
异步执行与同步执行最大的区别,就是异步执行不会立刻执行任务,而是将任务提交到待执行的队列中等待调度。所以在异步执行中,会首先将 block 封装成 dispatch_continuation_t 再做处理:
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME;
dispatch_qos_t qos;
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
return dx_push(dqu._dq, dc, qos);
}
#define dx_vtable(x) (&(x)->do_vtable->_os_obj_vtable)
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
在 _dispatch_continuation_init 中,将 block、flag 以及一些其他信息都装在了 dispatch_continuation_t 中,然后在 _dispatch_continuation_async 中进行后续的操作,后面的调用就属于比较迷惑的地方了,dx_push 本身是一个宏,它会找到 dispatch queue 的 do_vtable.os_obj_vtable 并调用它的 dq_push 函数,粗略一看好像突然没有头绪了,只能从创建队列的时候入手再查看一下,在 _dispatch_lane_create_with_target 中也确实找到了与 vtable 有关的,
const void *vtable;
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
if (dqai.dqai_concurrent) {
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
#define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)
#define DISPATCH_OBJC_CLASS(name) (&DISPATCH_CLASS_SYMBOL(name))
#define DISPATCH_CLASS_SYMBOL(name) _dispatch_##name##_vtable
由此可见,对于并行队列而言,它的 vtable 就是 _dispatch_queue_concurrent_vtable,串行队列的为 _dispatch_queue_serial_vtable,而我们可以在 init.c 中找到它们的声明,
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_serial, lane,
.do_type = DISPATCH_QUEUE_SERIAL_TYPE,
.do_dispose = _dispatch_lane_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_lane_invoke,
.dq_activate = _dispatch_lane_activate,
.dq_wakeup = _dispatch_lane_wakeup,
.dq_push = _dispatch_lane_push,
);
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_concurrent, lane,
.do_type = DISPATCH_QUEUE_CONCURRENT_TYPE,
.do_dispose = _dispatch_lane_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_lane_invoke,
.dq_activate = _dispatch_lane_activate,
.dq_wakeup = _dispatch_lane_wakeup,
.dq_push = _dispatch_lane_concurrent_push,
);
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_global, lane,
.do_type = DISPATCH_QUEUE_GLOBAL_ROOT_TYPE,
.do_dispose = _dispatch_object_no_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_object_no_invoke,
.dq_activate = _dispatch_queue_no_activate,
.dq_wakeup = _dispatch_root_queue_wakeup,
.dq_push = _dispatch_root_queue_push,
);
#define DISPATCH_VTABLE_SUBCLASS_INSTANCE(name, ctype, ...) \
OS_OBJECT_VTABLE_SUBCLASS_INSTANCE(dispatch_##name, dispatch_##ctype, \
_dispatch_xref_dispose, _dispatch_dispose, \
.do_kind = #name, __VA_ARGS__)
#define OS_OBJECT_VTABLE_SUBCLASS_INSTANCE(name, ctype, xdispose, dispose, ...) \
const struct ctype##_vtable_s OS_OBJECT_CLASS_SYMBOL(name) = { \
._os_obj_xref_dispose = xdispose, \
._os_obj_dispose = dispose, \
._os_obj_vtable = { __VA_ARGS__ }, \
}
所以,在 _dispatch_continuation_async 中调用的 dx_push,最终映射过来,就是 _dispatch_root_queue_push 这些,而不同类型的队列映射的函数是不一样的,一般常见的就是 _dispatch_lane_push(串行队列),_dispatch_lane_concurrent_push)(并行队列),_dispatch_root_queue_push(global queue),分别看下。
串行队列
串行队列中的 dx_push 函数为 _dispatch_lane_push,
void
_dispatch_lane_push(dispatch_lane_t dq, dispatch_object_t dou,
dispatch_qos_t qos)
{
dispatch_wakeup_flags_t flags = 0;
struct dispatch_object_s *prev;
// 首先判断 block 是不是一个 waiter 类型的 block
// 比如调用 dispatch_barrier_async_and_wait,
// 比如在 _dispatch_sync_f_slow 中将同步任务通过 dispatch_sync_and_wait 提交
if (unlikely(_dispatch_object_is_waiter(dou))) {
return _dispatch_lane_push_waiter(dq, dou._dsc, qos);
}
dispatch_assert(!_dispatch_object_is_global(dq));
qos = _dispatch_queue_push_qos(dq, qos);
// If we are going to call dx_wakeup(), the queue must be retained before
// the item we're pushing can be dequeued, which means:
// - before we exchange the tail if we have to override
// - before we set the head if we made the queue non empty.
// Otherwise, if preempted between one of these and the call to dx_wakeup()
// the blocks submitted to the queue may release the last reference to the
// queue when invoked by _dispatch_lane_drain. <rdar://problem/6932776>
// 将当前任务加到任务链表的 tail
prev = os_mpsc_push_update_tail(os_mpsc(dq, dq_items), dou._do, do_next);
if (unlikely(os_mpsc_push_was_empty(prev))) {
_dispatch_retain_2_unsafe(dq);
flags = DISPATCH_WAKEUP_CONSUME_2 | DISPATCH_WAKEUP_MAKE_DIRTY;
} else if (unlikely(_dispatch_queue_need_override(dq, qos))) {
// There's a race here, _dispatch_queue_need_override may read a stale
// dq_state value.
//
// If it's a stale load from the same drain streak, given that
// the max qos is monotonic, too old a read can only cause an
// unnecessary attempt at overriding which is harmless.
//
// We'll assume here that a stale load from an a previous drain streak
// never happens in practice.
_dispatch_retain_2_unsafe(dq);
flags = DISPATCH_WAKEUP_CONSUME_2;
}
// 将 prev 的 next 设置为当前任务
os_mpsc_push_update_prev(os_mpsc(dq, dq_items), prev, dou._do, do_next);
// 如果需要唤醒,就调用 dx_wakeup
if (flags) {
return dx_wakeup(dq, qos, flags);
}
}
串行队列的 dx_wakeup,通过查表可知是 _dispatch_queue_wakeup,上面也说到过,所有用户创建的队列都是不管理线程的,它只会把自己的任务再提交给 target queue,所以此处的串行队列的 dx_wakup,就没有关于线程的操作,它只是把自己当成一个任务,继续提交给了它的 target queue。
并行队列
并行队列中的 dx_push 函数为 _dispatch_lane_concurrent_push,
void
_dispatch_lane_concurrent_push(dispatch_lane_t dq, dispatch_object_t dou,
dispatch_qos_t qos)
{
// <rdar://problem/24738102&24743140> reserving non barrier width
// doesn't fail if only the ENQUEUED bit is set (unlike its barrier
// width equivalent), so we have to check that this thread hasn't
// enqueued anything ahead of this call or we can break ordering
if (dq->dq_items_tail == NULL &&
!_dispatch_object_is_waiter(dou) &&
!_dispatch_object_is_barrier(dou) &&
_dispatch_queue_try_acquire_async(dq)) {
return _dispatch_continuation_redirect_push(dq, dou, qos);
}
_dispatch_lane_push(dq, dou, qos);
}
当满足一定的条件时,它会调用 _dispatch_continuation_redirect_push 直接将任务通过 dx_push 提交给它的 target queue,否则就还是跟串行队列的一样,通过 _dispatch_lane_push 提交。
global queue
所以在异步执行里面,要想了解任务什么时候执行、在哪个线程执行,最需要看的就是 global queue 中 dx_push 的实现,也就是 _dispatch_root_queue_push:
static inline void
_dispatch_root_queue_push_inline(dispatch_queue_global_t dq,
dispatch_object_t _head, dispatch_object_t _tail, int n)
{
struct dispatch_object_s *hd = _head._do, *tl = _tail._do;
if (unlikely(os_mpsc_push_list(os_mpsc(dq, dq_items), hd, tl, do_next))) {
return _dispatch_root_queue_poke(dq, n, 0);
}
}
#define os_mpsc_push_list(Q, head, tail, _o_next) ({ \
os_mpsc_node_type(Q) _token; \
_token = os_mpsc_push_update_tail(Q, tail, _o_next); \
os_mpsc_push_update_prev(Q, _token, head, _o_next); \
os_mpsc_push_was_empty(_token); \
})
os_mpsc_push_list 这个宏的的逻辑就是,将 head 到 tail 的这一段链表加到当前队列的待执行链表中,然后返回在此之前的链表是否为空,如果为空意味着当前没有在执行的线程,所以需要调用 _dispatch_root_queue_poke 开启线程处理任务,大概就是这样。这个函数的基本流程就是,
- 进行队列初始化,创建线程池什么的
- 计算出需要创建的线程数量
- 创建线程,处理任务
新建线程的启动函数是 _dispatch_worker_thread,它的工作就是不断地从待执行任务链表中取出来头节点,然后执行。取头节点的函数为 _dispatch_root_queue_drain_one,执行的函数为 _dispatch_continuation_pop_inline,
static inline void
_dispatch_continuation_pop_inline(dispatch_object_t dou,
dispatch_invoke_context_t dic, dispatch_invoke_flags_t flags,
dispatch_queue_class_t dqu)
{
dispatch_pthread_root_queue_observer_hooks_t observer_hooks =
_dispatch_get_pthread_root_queue_observer_hooks();
if (observer_hooks) observer_hooks->queue_will_execute(dqu._dq);
flags &= _DISPATCH_INVOKE_PROPAGATE_MASK;
if (_dispatch_object_has_vtable(dou)) {
dx_invoke(dou._dq, dic, flags);
} else {
_dispatch_continuation_invoke_inline(dou, flags, dqu);
}
if (observer_hooks) observer_hooks->queue_did_execute(dqu._dq);
}
这里是有对 item 的类型的判断的,因为从之前的描述也可以看到,装载到 global queue 的任务链表中的除了一个一个的任务(dispatch_continuation_t)外,用户自己创建的队列也是一种任务,所以它们要通过 dx_invoke 来执行自己内部的任务,这里串行队列和并行队列都是一个函数 _dispatch_lane_invoke,最终负责执行的是 _dispatch_lane_drain,这里跟 _dispatch_worker_thread 其实是差不多的,就是不断地从队列的任务链表中取任务,然后调用 _dispatch_continuation_pop_inline 执行,然后继续判断当前的任务是一个队列,还是真实的任务,如此往复,直到一个队列及它的所有子队列(这样描述不一定对,就简单理解为子队列就意味着 target queue 指向这个队列的所有队列好了)中的所有任务为止。
以上就是整个,任务从提交到执行的过程,当然还有很多细节的地方没有照顾到,后续有机会继续。