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 的功能类型来说, 它有两种,分别是串行队列和并行队列,简单来说,

  1. 给到串行队列的所有任务,都会按照先后顺序依次执行,但是具体在哪个线程执行并不确定
  2. 给到并行队列的所有任务,一般会直接执行,但执行在哪个线程也是不能确定的

功用类型

从功用类型来说,可以将 dispatch queue 划分为系统创建的 global queue,main queue 和用户自己创建的 queue,它们最根本的区别在于,系统创建的 queue 是真实可以执行任务的,而用户创建的 queue 只起到一个控制任务什么时候可以提交给系统创建的的任务的作用,他们之间,就是依赖 target queue。

  1. global queue 是系统内置的一些不同优先级的并行队列,一共五种
  2. mian queue 是主队列,它有且只有一个运行线程,就是主线程

我们自己创建的 dispatch queue,不论是并行队列还是串行队列,它们默认的 target queue 都是 QOS_CLASS_DEFAULT 类型的 global queue,也就是说最终它们都是要执行在 global queue 队列上,但是因为这是一个并行队列,所以从使用上来说,并不能感知到什么。

功能

dispatch queue 有两个最主要的功能就是执行任务,分别是同步执行 dispatch_sync 和异步执行 dispatch_async。

  1. 一般来说,所有使用 dispatch_sync 执行的任务,线程会阻塞,等待任务执行完毕才会继续执行下一个命令,即,同步执行的任务只是使用了队列的 FIFO 功能,其执行的线程还是当前线程
  2. 所有使用 dispatch_async 执行的命令,都不会阻塞当前线程,系统会为其选择一个合适的线程执行任务

任务与线程

正因为 dispatch queue 只是对多线程的一种封装,所以就会导致有些时候我们将一个任务交给队列执行之后,都不清楚它到底是运行在哪个线程,在何时执行的。先说结论:

并行队列串行队列
当前队列 dispatch async开启新线程不开启线程,当前任务执行完后执行
当前队列 dispatch sync不开启线程,当前队列执行死锁
其他队列 dispatch async开启新线程开启新线程
其他队列 dispatch sync不开启线程,当前队列执行不开启线程,当前队列执行

如上所示,就是当不同类型的队列遇到不同类型的执行方式后产生的反应。总结起来就是下面几点:

  1. dispatch sync 在当前线程执行,但需要依赖按照队列决定什么时候才能执行
  2. dispatch async 执行线程由队列决定,一般是立刻就能执行
  3. 串行队列最多开启一个线程,所有任务都要依次执行
  4. 并行队列可开启多个线程,任务一般是给过来就可以执行

代码分析

单从理论分析显得干瘪,代码才能说明一切,下面就从 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)));
}

从代码中可以看出,无论是并行队列还是串行队列,它们都会有一个先判断的过程,但是区别还是有的:

  1. 在并行队列中,是判断是否有 barrier 类型的任务在执行,并将正在执行的任务数量记录下,对应函数为 _dispatch_queue_try_reserve_sync_width
  2. 在串行队列中,是判断是否有任务在执行,对应函数 _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,有两种执行方式,

  1. 如果当前不需要等待,就在当前线程直接执行
  2. 如果需要等待其他任务执行完,就会通过类似 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 开启线程处理任务,大概就是这样。这个函数的基本流程就是,

  1. 进行队列初始化,创建线程池什么的
  2. 计算出需要创建的线程数量
  3. 创建线程,处理任务

新建线程的启动函数是 _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 指向这个队列的所有队列好了)中的所有任务为止。

以上就是整个,任务从提交到执行的过程,当然还有很多细节的地方没有照顾到,后续有机会继续。