【iOS多线程】DispatchQueue原理解析之队列创建

1,123 阅读9分钟

本文是作者在学习iOS底层代码时所著,文章中会尽量使用确定性的词汇,旨在帮助想要轻度深入的读者能够快速了解iOS底层实现。文章中内容的逻辑自洽,但内容的正确性需要读者自行甄别,仅供参考。

上篇文章中介绍了DispatchQueue中的基础用法,但也产生了一些疑问,在这篇文章中,我们先来看看,队列是如何创建的。

数据结构

我们平时所说的队列就是dispatch_queue_s,根据dispatch_queue_s源码,再结合这个结构体在源码中的具体使用,列出了它的几个比较重要的属性:

struct dispatch_queue_s {
    // 熟悉一下这些属性
    unsigned long dq_serialnum; // 队列的序号
    const char *dq_label; // 标签
    dispatch_priority_t dq_priority; // 优先级
    struct dispatch_queue_s *volatile do_next;
    struct dispatch_queue_s *do_targetq; // target
    void *do_ctxt;
    struct dispatch_object_s *volatile dq_items_tail
    
    const struct dispatch_queue_vtable_s *do_vtable; // 虚拟表
    do_ref_cnt; // 引用计数
    do_xref_cnt; // 外部引用计数

    uint64_t volatile dq_state,
    dispatch_lock dq_state_lock,
    uint32_t dq_state_bits

接下来,我们看看dispatch_queue_s是如何创建的。

关于dispatch_queue_sdispatch_queue_t理解为相同即可

队列创建

在swift中我们是这样创建一个队列的

let queue = DispatchQueue(label: "com.concurrent", attributes: .concurrent)

相对应的源码为dispatch_queue_create

// 这里dispatch_queue_t直接理解为dispatch_queue_s即可
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
	return _dispatch_lane_create_with_target(label, attr,
			DISPATCH_TARGET_QUEUE_DEFAULT, true);
}

其中传入的label和attr就如字面意思一样,接着看_dispatch_lane_create_with_target

DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
		dispatch_queue_t tq, bool legacy)
{
	// 一开始我们获取到一个dqai,它包含了一些创建队列时需要的参数,后面会详细分析获取的具体实现
	dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

	// 这里官方的注释已经告诉了我们,第一步是标准化参数,这些参数就是指qos,overcommit和ta,
	// 我们就以这个为线索接着往下看

	//
	// Step 1: Normalize arguments (qos, overcommit, tq)
	//

	// 标准化qos
	dispatch_qos_t qos = dqai.dqai_qos;
	// 在!HAVE_PTHREAD_WORKQUEUE_QOS这种条件下,DISPATCH_QOS_USER_INTERACTIVE和DISPATCH_QOS_MAINTENANCE两种状态不存在,
	// 所以分别调整为DISPATCH_QOS_USER_INITIATED和DISPATCH_QOS_BACKGROUND。
#if !HAVE_PTHREAD_WORKQUEUE_QOS
	if (qos == DISPATCH_QOS_USER_INTERACTIVE) {
		dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;
	}
	if (qos == DISPATCH_QOS_MAINTENANCE) {
		dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;
	}
#endif // !HAVE_PTHREAD_WORKQUEUE_QOS

	// 标准化overcommit
	_dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;
	// 在使用diapatch_create_queue()创建队列时,传入的tq是DISPATCH_TARGET_QUEUE_DEFAULT这个宏,而这个宏对应的值为NULL
	// 所以在这种情况下if的条件表达式总为false,不会执行
	if (overcommit != _dispatch_queue_attr_overcommit_unspecified && tq) {
		if (tq->do_targetq) {
			DISPATCH_CLIENT_CRASH(tq, "Cannot specify both overcommit and "
					"a non-global target queue");
		}
	}

	// 同理,这里也不会执行
	if (tq && dx_type(tq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE) {
		// Handle discrepancies between attr and target queue, attributes win
		if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
			if (tq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) {
				overcommit = _dispatch_queue_attr_overcommit_enabled;
			} else {
				overcommit = _dispatch_queue_attr_overcommit_disabled;
			}
		}
		if (qos == DISPATCH_QOS_UNSPECIFIED) {
			qos = _dispatch_priority_qos(tq->dq_priority);
		}
		tq = NULL;
	} 
	// 同理,这里也不会执行
	else if (tq && !tq->do_targetq) {
		// target is a pthread or runloop root queue, setting QoS or overcommit
		// is disallowed
		if (overcommit != _dispatch_queue_attr_overcommit_unspecified) {
			DISPATCH_CLIENT_CRASH(tq, "Cannot specify an overcommit attribute "
					"and use this kind of target queue");
		}
	} 
	// 最终会执行这里的代码
	else {
		// 这里我们直接假定overcommit等于_dispatch_queue_attr_overcommit_unspecified,
		if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
			// Serial queues default to overcommit!

			// 串行队列启动overcommit,并行队列关闭overcommit
			overcommit = dqai.dqai_concurrent ?
					_dispatch_queue_attr_overcommit_disabled :
					_dispatch_queue_attr_overcommit_enabled;
		}
	}

	// 标准化tq
	// tq为NULL,所以会走if表达式进行赋值
	if (!tq) {
		// _dispatch_get_root_queue这个函数的内容很多,这里我们先从字面意思去理解,这个函数是获取root队列,
		// 并且这个root队列是根据我们传入了qos和overcommit而获得的,我们先不去管内部发生了什么,接着往下看
		tq = _dispatch_get_root_queue(
				qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
				overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
		if (unlikely(!tq)) {
			DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
		}
	}

	// 从官方的注释中能够了解到,第一步已经结束了,其中的qos, overcommit, tq都已经标准化(被合理地赋值了),
	// 现在开始第二步,初始化队列

	//
	// Step 2: Initialize the queue
	//

	if (legacy) {
		// if any of these attributes is specified, use non legacy classes
		// 这里dqai_inactive为0,dqai_autorelease_frequency为DISPATCH_AUTORELEASE_FREQUENCY_INHERIT,也为0,按理说表达式不成立,
		// 但多篇文章中都说了这样一句话"默认是会给dqa_autorelease_frequency指定为DISPATCH_AUTORELEASE_FREQUENCY_INHERIT,所以这个判断式是成立的",
		// 可能以前的代码中DISPATCH_AUTORELEASE_FREQUENCY_INHERIT值大于0吧,我们按照表达式不成立来理解,
		// 接下来的代码不会执行,legacy依然为true
		if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {
			legacy = false;
		}
	}

	// 这个参数很重要,最终是由它来执行任务
	const void *vtable;

	// 由于legacy为true,dqf会被赋值为DQF_MUTABLE,即便我们分析错了,目前来说问题也不大,因为我们后面不会看dqf的值,这里也只是做一个了解
	dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
	if (dqai.dqai_concurrent) {
		// vtable被赋值为OS_dispatch_queue_concurrent_class
		vtable = DISPATCH_VTABLE(queue_concurrent);
	} else {
		// vtable被赋值为OS_dispatch_queue_serial_class
		vtable = DISPATCH_VTABLE(queue_serial);
	}

	// dqai_autorelease_frequency为DISPATCH_AUTORELEASE_FREQUENCY_INHERIT,不会走任何一个case
	switch (dqai.dqai_autorelease_frequency) {
	case DISPATCH_AUTORELEASE_FREQUENCY_NEVER:
		dqf |= DQF_AUTORELEASE_NEVER;
		break;
	case DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM:
		dqf |= DQF_AUTORELEASE_ALWAYS;
		break;
	}

	if (label) {
		const char *tmp = _dispatch_strdup_if_mutable(label);
		// strdup是copy的意思,表面上看tmp跟label会相等吧
		// 就认为表达式为false,以下代码不执行
		if (tmp != label) {
			dqf |= DQF_LABEL_NEEDS_FREE;
			label = tmp;
		}
	}

	// 为dq分配内存空间,让dq持有vtable
	dispatch_lane_t dq = _dispatch_object_alloc(vtable,
			sizeof(struct dispatch_lane_s));
	// 根据dqf,是否为串行/并行队列,是否激活来初始化dq
	_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
			DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
			(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));

	// 继续初始化dq
	dq->dq_label = label;
	dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
			dqai.dqai_relpri);
	if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
		// overcommit的信息存储在dq_priority中
		dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
	}
	if (!dqai.dqai_inactive) {
		// 从tq中继承优先级配置
		_dispatch_queue_priority_inherit_from_target(dq, tq);
		// 从tq中继承wlh信息
		_dispatch_lane_inherit_wlh_from_target(dq, tq);
	}
	_dispatch_retain(tq);

	// 依赖tq,最终执行任务的队列是tq
	dq->do_targetq = tq;
	_dispatch_object_debug(dq, "%s", __func__);
	return _dispatch_trace_queue_create(dq)._dq;
}

从源码的注释中,我们可以大致了解到队列(dispatch_queue_t)创建的过程,其中有两点非常重要,

  1. 从root队列中获取到一个队列,然后设置该队列为target,这意味着我们创建的队列只是一个代理,真正执行任务的是获取到的root队列;
  2. 根据队列类型(串行、并行)初始化队列(dispatch_queue_t)中的vtable,vtable存放了队列执行任务的各种逻辑;

接下来我们一起来详细了解下,它们的具体实现,

root queue

static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
	if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
		DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");
	}

	// qos的值为1...6, overcommit为0或1
	// 2 * (qos - 1) + overcommit的值为0...11一共12个队列
	// 其中双数0, 2, 4, 6, 8, 10为普通队列,单数1, 3, 5, 7, 9, 11为过载(overcommit)队列
	return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}

接着看_dispatch_root_queues

struct dispatch_queue_global_s _dispatch_root_queues[] = {
#define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \
		((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \
		DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \
		DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)
#define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \
	[_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \
		DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \
		.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \
		.do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \
		.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
		.dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \
				_dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \
				_dispatch_priority_make(DISPATCH_QOS_##n, 0)), \
		__VA_ARGS__ \
	}
	_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,
		.dq_label = "com.apple.root.maintenance-qos",
		.dq_serialnum = 4,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.maintenance-qos.overcommit",
		.dq_serialnum = 5,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,
		.dq_label = "com.apple.root.background-qos",
		.dq_serialnum = 6,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.background-qos.overcommit",
		.dq_serialnum = 7,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,
		.dq_label = "com.apple.root.utility-qos",
		.dq_serialnum = 8,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.utility-qos.overcommit",
		.dq_serialnum = 9,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
		.dq_label = "com.apple.root.default-qos",
		.dq_serialnum = 10,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
			DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.default-qos.overcommit",
		.dq_serialnum = 11,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
		.dq_label = "com.apple.root.user-initiated-qos",
		.dq_serialnum = 12,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.user-initiated-qos.overcommit",
		.dq_serialnum = 13,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,
		.dq_label = "com.apple.root.user-interactive-qos",
		.dq_serialnum = 14,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.user-interactive-qos.overcommit",
		.dq_serialnum = 15,
	),
};

看到这里,获取root队列的源码就结束了,可能依然想问,这个有什么用呢?我们直接举个例子来解释root队列相关的内容,先回到我们最初队列创建的swift代码,

// 为了更好理解,我们明确地给qos赋值为.default
let queue = DispatchQueue(label: "com.concurrent", qos: .default, attributes: .concurrent)

然后我们直接使用qos来获取root队列,

// 根据定义,我们获取到qos为4,
#define DISPATCH_QOS_DEFAULT            ((dispatch_qos_t)4)
// 由于是并行队列overcommit为0,
// 2 * (qos - 1) + overcommit的计算结果为6
_dispatch_root_queues[2 * (qos - 1) + overcommit];

// 对应的结果为
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
    .dq_label = "com.apple.root.default-qos",
    .dq_serialnum = 10,
 )

最后的结论,当我们在qos为.default的多个自定义并行队列中执行任务时,任务都是交到了名称为com.apple.root.default-qos的root队列中来。

vtable

源码中,我们提到的重要参数vtable,在创建自定义并行队列时,是这样,在dispatch_async的解析中,会进行详细地分析,

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_queue_attr_to_info

除了刚刚提到的两个非常重要的内容,我们可以继续看看,在队列(dispatch_queue_t)创建的过程中,我们传入的attributes参数是如何工作的,

dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
	dispatch_queue_attr_info_t dqai = { };

	// 串行队列时dqa为nil,会返回只包含默认值的dqai,也就是其属性全是0
	if (!dqa) return dqai;

#if DISPATCH_VARIANT_STATIC
	if (dqa == &_dispatch_queue_attr_concurrent) {
		dqai.dqai_concurrent = true;
		return dqai;
	}
#endif

	// 判断dqa和_dispatch_queue_attrs的大小关系,
	if (dqa < _dispatch_queue_attrs ||
			dqa >= &_dispatch_queue_attrs[DISPATCH_QUEUE_ATTR_COUNT]) {
#ifndef __APPLE__
		if (memcmp(dqa, &_dispatch_queue_attrs[0],
				sizeof(struct dispatch_queue_attr_s)) == 0) {
			dqa = (dispatch_queue_attr_t)&_dispatch_queue_attrs[0];
		} else
#endif // __APPLE__
		// 如果越界,则报错
		DISPATCH_CLIENT_CRASH(dqa->do_vtable, "Invalid queue attribute");
	}

	// 这里可以看出,前面的判断是为了保证计算出的idx处于合理范围
	size_t idx = (size_t)(dqa - _dispatch_queue_attrs);

	// 根据idx生成具体的值
	// 这里的计算能比较容易看出,是在按位取值,
	// 根据dqa提供的数据初始化dqai,
	// 不难看出,只要知道idx的值,下面的值都能够通过计算获得,但我没看懂两个结构体相减应该是多少
	// 不过,后面我会按照我的理解,推测出其中的一些值
	dqai.dqai_inactive = (idx % DISPATCH_QUEUE_ATTR_INACTIVE_COUNT);
	idx /= DISPATCH_QUEUE_ATTR_INACTIVE_COUNT;

	dqai.dqai_concurrent = !(idx % DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT);
	idx /= DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT;

	dqai.dqai_relpri = -(int)(idx % DISPATCH_QUEUE_ATTR_PRIO_COUNT);
	idx /= DISPATCH_QUEUE_ATTR_PRIO_COUNT;

	dqai.dqai_qos = idx % DISPATCH_QUEUE_ATTR_QOS_COUNT;
	idx /= DISPATCH_QUEUE_ATTR_QOS_COUNT;

	dqai.dqai_autorelease_frequency =
			idx % DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
	idx /= DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;

	dqai.dqai_overcommit = idx % DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
	idx /= DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;

	return dqai;
}

为了能够知道dqai的属性的初始化值,我们需要看一些swift的源码

// 这是DispatchQueue的初始化方法,我们假设传入的参数都是默认值,
public convenience init(
    label: String,
    qos: DispatchQoS = .unspecified,
    attributes: Attributes = [],
    autoreleaseFrequency: AutoreleaseFrequency = .inherit,
    target: DispatchQueue? = nil)
{
    // 这里获取的attr,就是之前的dqa,并且后面的代码不会更改attr
    var attr = attributes._attr()

    // 这里为false,不会执行
    if autoreleaseFrequency != .inherit {
        attr = autoreleaseFrequency._attr(attr: attr)
    }

    // 这里也为false,不会执行
    if #available(macOS 10.10, iOS 8.0, *), qos != .unspecified {
        attr = __dispatch_queue_attr_make_with_qos_class(attr, qos.qosClass.rawValue, Int32(qos.relativePriority))
    }

    // 这里走required的初始化方法
    if #available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
        self.init(__label: label, attr: attr, queue: target)
    } else {
        self.init(__label: label, attr: attr)
        if let tq = target { self.setTarget(queue: tq) }
    }
}

继续看attributes._attr()

public struct Attributes : OptionSet {
    public let rawValue: UInt64
    public init(rawValue: UInt64) { self.rawValue = rawValue }

    public static let concurrent = Attributes(rawValue: 1<<1)

    @available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)
    public static let initiallyInactive = Attributes(rawValue: 1<<2)

    fileprivate func _attr() -> __OS_dispatch_queue_attr? {
        var attr: __OS_dispatch_queue_attr?

        // 当队列为串行队列时,attr最终返回nil,
        // 当队列为并行队列时,attr会被赋值
        if self.contains(.concurrent) {
            attr = _swift_dispatch_queue_concurrent()
        }

        // 表达式为false,不会执行
        if #available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
            if self.contains(.initiallyInactive) {
                attr = __dispatch_queue_attr_make_initially_inactive(attr)
            }
        }

        // 返回值为nil
        return attr
    }
}

至此,可以看出来,串行队列创建时,attr传入的值为nil,因此dpa也为空,所以串行队列返回的dpai是一个空的结构体,也就是里面的值都为0。

为了弄清楚并行队列的dpai的值的情况,我们还要再看回过头看一下swift的队列初始化方法,

convenience init(label: String,
                 qos: DispatchQoS = .unspecified,
                 attributes: DispatchQueue.Attributes = [],
                 autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit,
                 target: DispatchQueue? = nil)

从中很容易知道,qos为.unspecified,attributes可选值为.concurrent.initiallyInactive,但默认都没有传,autoreleaseFrequency的值为.inherit,再结合dpqi中的参数,我们可以得到,

dqai.dqai_inactive = 0  // attributes没传入.initiallyInactive
dqai.dqai_concurrent = 1 // attributes传入.concurrent
dqai.dqai_relpri = ? // 依然不知道
dqai.dqai_qos = DISPATCH_QOS_UNSPECIFIED // 根据名字推断对应的是这个宏,这个宏代表的是0
dqai.dqai_autorelease_frequency = DISPATCH_AUTORELEASE_FREQUENCY_INHERIT // 根据名字推断对应的是这个宏,这个宏代表的是0
dqai.dqai_overcommit = 0 // 猜测应该为0,然后创建队列时,根据是串行队列还是并行队列,来确定过载(overcommit)参数的值

结语

由于能力有限,文章中难免出现疏漏或者根本性错误,欢迎指正。

如果文章对您有帮助,可以点个赞支持一下,谢谢!

接下来

现在我们已经知道一个队列是如何创建的了。我们也知道,当我们把一个异步任务添加到队列中的时候,这个添加操作不会阻塞当前线程,这个异步任务会在一段时间之后,在某个线程中被执行。 那么,这个异步任务被添加到哪里了呢?任务添加之后,什么时候执行呢?执行任务的线程时从哪里来的呢? 为了回答这些问题,在下一篇文章中,我们一起来看看dispatch_async的源码。

参考资料

queue.c