iOS底层原理探索 之 GCD原理(上)

2,000 阅读12分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)
  13. iOS 底层原理探索 之 应用程序加载原理dyld (下)
  14. iOS 底层原理探索 之 类的加载
  15. iOS 底层原理探索 之 分类的加载
  16. iOS 底层原理探索 之 关联对象
  17. iOS底层原理探索 之 魔法师KVC
  18. iOS底层原理探索 之 KVO原理|8月更文挑战
  19. iOS底层原理探索 之 重写KVO|8月更文挑战
  20. iOS底层原理探索 之 多线程原理|8月更文挑战
  21. iOS底层原理探索 之 GCD函数和队列

以上内容的总结专栏


细枝末节整理


前言

在上一篇章 GCD函数和队列 中,我们着重对于GCD在使用函数和队列整理了相关的概念和总结,今天开始,我们 对GCD的底层数据结构和同步异步任务的底层执行逻辑开始研究,探索其使用技巧、底层原理以及其面试中的坑点。好的,话不多说,这就开始今天的内容,大家加油!!

GCD的底层数据结构

通过上面章节对于GCD的理论知识的介绍,我们最关心的是,在底层GCD究竟是如何实现的。下面我们就开始一步步从OC层的调用来深入到底层来看GCD的底层实现。

从一道面试题开始

    //打印顺序 是什么?

    dispatch_queue_t queue = dispatch_queue_create("com.superman.cn", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"1");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2");
    });

    dispatch_sync(queue, ^{ NSLog(@"3"); });
    
    NSLog(@"0");

    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });

首先,queue 是一条并发队列, 1、2是异步添加的所以,其顺序确定不了, 3 是同步添加的,所以 1、2、3的顺序也是不确定的, 但是3会堵塞其后面的0, 那么, 3 是要在 0 前面执行的,而后面的 7、8、9 均是异步添加的,所以他们的顺序也是不固定的,但是,线程添加异步的任务是要耗时的,所以,7、8、9 大概率是要在0之后执行。

队列

队列一共分两种 串行队列 和 并发队列。 我们平时使用中大致会接触到以下四种队列

    // 串行队列
    dispatch_queue_t serial = dispatch_queue_create("kc", DISPATCH_QUEUE_SERIAL);
    
    // 并发队列
    dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    
    // 主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    // 全局并发队列
    dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);

我们都知道主队列是串行队列,但是,为什么他是串行队列呢?

常规操作我们先点进去看看:

/*!
 * @function dispatch_get_main_queue
 *
 * @abstract
 * Returns the default queue that is bound to the main thread.
 *
 * @discussion
 * 为了调用提交给主队列的块,应用程序必须 
 * 调用dispatch_main(), NSApplicationMain(),
 * 或者在main上使用CFRunLoop 线程。
 *
 * 主队列用于在应用程序上下文中进行交互 主线程和主运行循环。
 *
 * 因为主队列的行为不完全像普通的串行队列, 
 * 当在非UI应用程序的进程中使用时,它可能会有不想要的副作用
 *  (守护进程)。对于这样的进程,应该避免主队列。
 *
 * @see dispatch_queue_main_t
 *
 * @result
 * 返回主队列。这个队列是代表自动创建的 
 * 调用main()之前的主线程。
 */
DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
	return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}

注释的内容也进一步说明了 dispatch_get_main_queue 其实就是一条 串行队列。下面,我们通过源码进一步论证。

下一个断点,查看下堆栈信息:

image.png

我们看到,调用了底层 block_invoke , 更下层的调用来自 libdispatch.dylib ,所以,我们要去苹果 Source Browser 下载一份源码然后继续分析。

dispatch_get_main_queue 源码分析

源码下载好打开后,直接搜索我们要研究的 dispatch_get_main_queue

dispatch_queue_main_t
dispatch_get_main_queue(void)
{
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}

接着,查找 DISPATCH_GLOBAL_OBJECT

#define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))
...
#define DISPATCH_GLOBAL_OBJECT(type, object) (static_cast<type>(&(object)))
...
#define DISPATCH_GLOBAL_OBJECT(type, object) ((type)&(object))

可以看到, 第二个参数 _dispatch_main_q 是 object, 从命名上可以得知,它才是 真正的对象, 第一个参数是它的类型。

所以,重点来到 _dispatch_main_q 的探索。 搜索 _dispatch_main_q 之后,结果很多,没有我们想要的内容,加 ( 后搜索直接没有找到,所以,我们看看对他的赋值有哪些操作, _dispatch_main_q =后,找到了我们想要的东西:

// 6618342 Contact the team that owns the Instrument DTrace probe before
//         renaming this symbol
struct dispatch_queue_static_s _dispatch_main_q = {
	DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
	.do_targetq = _dispatch_get_default_queue(true),
#endif
	.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
			DISPATCH_QUEUE_ROLE_BASE_ANON,
	.dq_label = "com.apple.main-thread",
	.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
	.dq_serialnum = 1,
};

找到其赋值之后,我们也看到了 其 label 的赋值,这也是在打印主线程信息的时候可以看到的,我们也可以通过搜索 com.apple.main-thread 而直接定位到 此处的源码。

好的,接着我们看如何去分析其是串行的队列。分析其内部的属性之后,对于 dq_atomic_flagsdq_serialnum 是关于串行队列比较接近的内容, 因为

.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
.dq_serialnum = 1,

目前,我们还是无法直接确定以上是和串行队列必然联系的内容。我们还需要确认串行队列必然有的一些特性,然后,结合这源码中对_dispatch_main_q 赋值的内容,才可以确定,_dispatch_main_q 就是串行队列。

下面我们,分析下,串行队列和并发队列的特性,从创建开始。

dispatch_queue_create

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);
}

_dispatch_lane_create_with_target

顺着 dispatch_queue_create 我们就来到了 _dispatch_lane_create_with_target

我们比较关心是创建函数的返回值,所以,其内部实现代码重点跟返回值。

    // 这里的 trace 是苹果底层对于痕迹的追踪
    // 重点 是此处的 dq
    return _dispatch_trace_queue_create(dq)._dq;

接着追踪 dq

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)
{

	...

	// 申请 并 开辟内存
	dispatch_lane_t dq = _dispatch_object_alloc(vtable,
			sizeof(struct dispatch_lane_s));
	// 初始化
	// dqai.dqai_concurrent ? DISPATCH_QUEUE_WIDTH_MAX : 1 
	// 是并发 - DISPATCH_QUEUE_WIDTH_MAX
	// 是串行 - 1
	_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_label = label;
	dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
			dqai.dqai_relpri);
	if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
		dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
	}
	if (!dqai.dqai_inactive) {
		_dispatch_queue_priority_inherit_from_target(dq, tq);
		_dispatch_lane_inherit_wlh_from_target(dq, tq);
	}
	_dispatch_retain(tq);
	dq->do_targetq = tq;
	_dispatch_object_debug(dq, "%s", __func__);
	return _dispatch_trace_queue_create(dq)._dq;
}

_dispatch_queue_init

static inline dispatch_queue_class_t
_dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf,
		uint16_t width, uint64_t initial_state_bits)
{
	uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);
	dispatch_queue_t dq = dqu._dq;

	dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |
			DISPATCH_QUEUE_INACTIVE)) == 0);

	if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {
		dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume
		if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {
			dq->do_ref_cnt++; // released when DSF_DELETED is set
		}
	}

	dq_state |= initial_state_bits;
	dq->do_next = DISPATCH_OBJECT_LISTLESS;
	dqf |= DQF_WIDTH(width);
	os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
	dq->dq_state = dq_state;
	dq->dq_serialnum =
			os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
	return dqu;
}

关于 第三个参数 width 的操作就 来到了

dqf |= DQF_WIDTH(width);

所以 dqf = 1 就是串行队列

我们接着再分析一下dq->dq_serialnum(它只是一个标识 = 17):

_dispatch_queue_serial_numbers

unsigned long volatile _dispatch_queue_serial_numbers = DISPATCH_QUEUE_SERIAL_NUMBER_INIT;

// skip zero
// 1 - main_q //这里的1代表主队列
// 2 - mgr_q
// 3 - mgr_root_q
// 4,5,6,7,8,9,10,11,12,13,14,15 - global queues
// 17 - workloop_fallback_q
// we use 'xadd' on Intel, so the initial value == next assigned
#define DISPATCH_QUEUE_SERIAL_NUMBER_INIT 17
extern unsigned long volatile _dispatch_queue_serial_numbers;

这里,我们在看一下全局并发队列:

dispatch_queue_global_s

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,
	),
};

这里,也进一步验证了 dq_serialnum 只是一个标识, 优先级是_DISPATCH_ROOT_QUEUE_ENTRY传入的第一个参数。

这里 dispatch_queue_global_s 是一个 stuct (默认赋值的结构体) 而且是一个集合的形式,随时使用,根据传参 随时取用。

从 dispatch_queue_t 开始

    //主队列类型
    dispatch_queue_main_t
    //全局并发队列类型
    dispatch_queue_global_t

可以看到队列的类型并不一致,那么,我们创建一个队列的时候使用一个dispatch_queue_t 类型来接, 那么,我们就从这个基础的类型来往下深入的推断。

DISPATCH_DECL(dispatch_queue);
...
#define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS(name, dispatch_object)
...
#define OS_OBJECT_DECL_SUBCLASS(name, super) \
OS_OBJECT_DECL_IMPL(name, NSObject, <OS_OBJECT_CLASS(super)>)
...
#define OS_OBJECT_DECL_IMPL(name, adhere, ...) \
	OS_OBJECT_DECL_PROTOCOL(name, __VA_ARGS__) \
	typedef adhere<OS_OBJECT_CLASS(name)> \
		* OS_OBJC_INDEPENDENT_CLASS name##_t
...
#define OS_OBJECT_DECL_PROTOCOL(name, ...) \
	@protocol OS_OBJECT_CLASS(name) __VA_ARGS__ \
	@end
...
#define OS_OBJECT_CLASS(name) OS_##name

上面的宏定义跳转比较多,我们来一个简单的 直接搜索#define DISPATCH_DECL(

DISPATCH_DECL(dispatch_queue);
...
#define DISPATCH_DECL(name) \
typedef struct name##_s : public dispatch_object_s {} *name##_t

也就得到了

typedef struct dispatch_queue_s : public dispatch_object_s {} * dispatch_queue_t 也就是 dispatch_queue_t 来自于 dispatch_queue_sdispatch_queue_s 继承自 dispatch_object_s; 看起来可能有点懵,给大家类比一下我们的class, class 继承自 objc_classobjc_class 继承自 objc_object

dispatch_object_s 的继承链

在上面的宏定义源码的下面,有一个 typedef union:

typedef union {
    struct _os_object_s *_os_obj;
    struct dispatch_object_s *_do;
    struct dispatch_queue_s *_dq;
    struct dispatch_queue_attr_s *_dqa;
    struct dispatch_group_s *_dg;
    struct dispatch_source_s *_ds;
    struct dispatch_channel_s *_dch;
    struct dispatch_mach_s *_dm;
    struct dispatch_mach_msg_s *_dmsg;
    struct dispatch_semaphore_s *_dsema;
    struct dispatch_data_s *_ddata;
    struct dispatch_io_s *_dchannel;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;

说明 dispatch_object_s 继承自 dispatch_object_t

struct dispatch_object_s {
    _DISPATCH_OBJECT_HEADER(object);
};

...

#define _DISPATCH_OBJECT_HEADER(x) \
    struct _os_object_s _as_os_obj[0]; \
    OS_OBJECT_STRUCT_HEADER(dispatch_##x); \
    struct dispatch_##x##_s *volatile do_next; \
    struct dispatch_queue_s *do_targetq; \
    void *do_ctxt; \
    union { \
            dispatch_function_t DISPATCH_FUNCTION_POINTER do_finalizer; \
            void *do_introspection_ctxt; \
    }
    
...


#define OS_OBJECT_STRUCT_HEADER(x) \
    _OS_OBJECT_HEADER(\
    const void *_objc_isa, \
    do_ref_cnt, \
    do_xref_cnt); \
    const struct x##_vtable_s *do_vtable


...

#define _OS_OBJECT_HEADER(isa, ref_cnt, xref_cnt) \
    isa; /* must be pointer-sized and use __ptrauth_objc_isa_pointer */ \
    int volatile ref_cnt; \
    int volatile xref_cnt

数据结构总结

在这里,其实通过 宏 一层一层的包装下去 其整个继承链的关系如下:

dispatch_queue_t -> dispatch_queue_sdispatch_queue_s -> _os_object_s -> dispatch_object_s

dispatch_sync任务块是如何调用的

dispatch_sync的使用

dispatch_sync(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"SuperMan 函数分析");
});

上面的Demo是我们最常使用的情况,我们向 dispatch_get_global_queue 添加了一个 blocck 代码块的任务来执行。 对于block我们知道,在定义好block之后,通过 block() 来调用,但是,使用dispatch_sync的时候,我们并没有看到调用,block中的任务也可以执行,这是如何做到的呢?我们接下来就剩入探究其内部实现。

首先来到内部实现:

void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
    uintptr_t dc_flags = DC_FLAG_BLOCK;
    if (unlikely(_dispatch_block_has_private_data(work))) {
            return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    }
    _dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

...

#define _dispatch_Block_invoke(bb) \
((dispatch_function_t)((struct Block_layout *)bb)->invoke)


_dispatch_sync_f

// 中间层封装
static void
_dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func,
		uintptr_t dc_flags)
{
    _dispatch_sync_f_inline(dq, ctxt, func, dc_flags);
}

_dispatch_sync_f_inline

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))) {
		return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);
	}

	if (unlikely(dq->do_targetq->do_targetq)) {
		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)));
}

方法中的内容我们并不知道会走到哪一条分支,我们可以通过下符号点来判断一下。

image.png

代码执行来到_dispatch_sync_f_slow:

_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;
	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_function_invoke

image.png

_dispatch_sync_function_invoke

static void
_dispatch_sync_function_invoke(dispatch_queue_class_t dq, void *ctxt,
dispatch_function_t func)
{
_dispatch_sync_function_invoke_inline(dq, ctxt, func);
}

_dispatch_sync_function_invoke_inline

static inline void
_dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt,
		dispatch_function_t func)
{
	dispatch_thread_frame_s dtf;
	_dispatch_thread_frame_push(&dtf, dq);
	_dispatch_client_callout(ctxt, func);
	_dispatch_perfmon_workitem_inc();
	_dispatch_thread_frame_pop(&dtf);
}

对于ctxtfunc有操作的是 _dispatch_client_callout(ctxt, func);

_dispatch_client_callout

void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
	_dispatch_get_tsd_base();
	void *u = _dispatch_get_unwind_tsd();
	if (likely(!u)) return f(ctxt);
	_dispatch_set_unwind_tsd(NULL);
	f(ctxt);
	_dispatch_free_unwind_tsd();
	_dispatch_set_unwind_tsd(u);
}

执行的是 f(ctxt);调用执行。

最后,在sync的block中打一个断点来做验证:

image.png

dispatch_async任务块是如何调用的

dispatch_async

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;
    // 任务的封装和优先级的封装
    // 异步函数代表异步调用 会无序 优先级是参考的依据 ; 任务的回调是异步的和CPU调度有关
    // 现存住,在某个时刻需要调用了,再拿出来调用
    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}

_dispatch_continuation_init

static inline dispatch_qos_t
_dispatch_continuation_init(dispatch_continuation_t dc,
		dispatch_queue_class_t dqu, dispatch_block_t work,
		dispatch_block_flags_t flags, uintptr_t dc_flags)
{
	void *ctxt = _dispatch_Block_copy(work);

	dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
	if (unlikely(_dispatch_block_has_private_data(work))) {
		dc->dc_flags = dc_flags;
		dc->dc_ctxt = ctxt;
		// will initialize all fields but requires dc_flags & dc_ctxt to be set
		return _dispatch_continuation_init_slow(dc, dqu, flags);
	}

	dispatch_function_t func = _dispatch_Block_invoke(work);
	if (dc_flags & DC_FLAG_CONSUME) {
		func = _dispatch_call_block_and_release;
	}
	return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}

_dispatch_continuation_init_f

static inline dispatch_qos_t
_dispatch_continuation_init_f(dispatch_continuation_t dc,
		dispatch_queue_class_t dqu, void *ctxt, dispatch_function_t f,
		dispatch_block_flags_t flags, uintptr_t dc_flags)
{
	pthread_priority_t pp = 0;
	dc->dc_flags = dc_flags | DC_FLAG_ALLOCATED;
	dc->dc_func = f;
	dc->dc_ctxt = ctxt;
	// in this context DISPATCH_BLOCK_HAS_PRIORITY means that the priority
	// should not be propagated, only taken from the handler if it has one
	if (!(flags & DISPATCH_BLOCK_HAS_PRIORITY)) {
		pp = _dispatch_priority_propagate();
	}
	_dispatch_continuation_voucher_set(dc, flags);
	return _dispatch_continuation_priority_set(dc, dqu, pp, flags);
}

synch中直接是 f(ctxt); 调用,这里却是

    dc->dc_func = f;
    dc->dc_ctxt = ctxt;

进行来一次包装,赋值给来dc;在最后,也进行了_dispatch_continuation_priority_set 对于优先级的处理。

在 异步函数 dispatch_async 中的 qos 那一行,也就是进行了任务的封装和优先级的处理。因为,异步函数是进行异步调用,执行是无序的,和CPU的调度有关系,系统需要将任务先存住,等待CPU的调度执行。那么,继续下面一行:

_dispatch_continuation_async


#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)


...



static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
		dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION
	if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
		_dispatch_trace_item_push(dqu, dc);
	}
#else
	(void)dc_flags;
#endif
	return dx_push(dqu._dq, dc, qos);
}

dx_vtable(x)->dq_push(x, y, z)

在这里,我们应该重点查看 -> 去调用的内容。 然后 dq_push 有很多的赋值内容, 到这里,就突然明白了,在dx_vtable(x) 中,其实是在判断当前的_dq是串行队列还是并发队列然后找到具体的内容,去执行相应的内容。

dq_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,
);

...

跟着这里去追踪下去是一条思路, 我们查看下堆栈信息会更快一点:

bt

image.png

可以看到最终执行到的是 _dispatch_client_callout

void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
	_dispatch_get_tsd_base();
	void *u = _dispatch_get_unwind_tsd();
	if (likely(!u)) return f(ctxt);
	_dispatch_set_unwind_tsd(NULL);
	f(ctxt);
	_dispatch_free_unwind_tsd();
	_dispatch_set_unwind_tsd(u);
}

最终是对任务的执行。

dispatch_async -> _dispatch_continuation_async -> dx_push -> _dispatch_root_queue_push -> _dispatch_root_queue_push_inline -> _dispatch_root_queue_poke -> _dispatch_root_queue_poke_slow -> _dispatch_root_queues_init -> dispatch_once_f(&_dispatch_root_queues_pred, NULL, _dispatch_root_queues_init_once) -> _dispatch_worker_thread2 -> _dispatch_root_queue_drain -> _dispatch_queue_override_invoke -> _dispatch_client_callout -> _dispatch_call_block_and_release

补充

_dispatch_queue_override_invoke

流程中没有发现对于 _dispatch_queue_override_invoke 的调用, 但是根据堆栈信息是在_dispatch_root_queue_drain之后却是来到了这里,此处的疑问,我们下一节再分晓。

从面试题结束

// 请问 分别打印多少?

- (void)MTDemo{
    while (self.num < 5) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.num++;
        });
    }
    NSLog(@"end : %d",self.num); 
}

- (void)KSDemo{
   
    for (int i= 0; i<10000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.num++;
        });
    }
    NSLog(@"end : %d",self.num); 
}

公布答案 :

  1. >= 5
  2. <= 10000 因为:
  • 第一,在while循环中,self.num 至少 ++ 5次之后,且num已经++完毕之后,才会跳出循环;这期间具体是添加了多少任务,就不得而知了,之后在打印的时候,那么至少也是5。

  • 第二,for循环中,循环只管向异步中添加10000次任务,最后在打印的时候,不管这些任务是否全部执行完。

  • 无论是同步还是异步去执行任务,在进行线程队列调度的时候,都会消耗一段时间。

这两道面试题,其实是在告诉我们,异步执行任务的时候,数据是不安全。

那么,如何才能保证我们的数据安全呢?这就需要在数据操作的时候根据情况加上,关于锁的内容我们会在后面的篇章进行详细探索。

扩展

atomic

即使在 num 属性申明的时候使用 atomic 也是无法保证数据的安全,因为,atomic只能保证setter和getter方法的安全,对于多线程异步读取数据,也无能为力。