这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
目录如下:
- iOS 底层原理探索 之 alloc
- iOS 底层原理探索 之 结构体内存对齐
- iOS 底层原理探索 之 对象的本质 & isa的底层实现
- iOS 底层原理探索 之 isa - 类的底层原理结构(上)
- iOS 底层原理探索 之 isa - 类的底层原理结构(中)
- iOS 底层原理探索 之 isa - 类的底层原理结构(下)
- iOS 底层原理探索 之 Runtime运行时&方法的本质
- iOS 底层原理探索 之 objc_msgSend
- iOS 底层原理探索 之 Runtime运行时慢速查找流程
- iOS 底层原理探索 之 动态方法决议
- iOS 底层原理探索 之 消息转发流程
- iOS 底层原理探索 之 应用程序加载原理dyld (上)
- iOS 底层原理探索 之 应用程序加载原理dyld (下)
- iOS 底层原理探索 之 类的加载
- iOS 底层原理探索 之 分类的加载
- iOS 底层原理探索 之 关联对象
- iOS底层原理探索 之 魔法师KVC
- iOS底层原理探索 之 KVO原理|8月更文挑战
- iOS底层原理探索 之 重写KVO|8月更文挑战
- iOS底层原理探索 之 多线程原理|8月更文挑战
- 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
其实就是一条 串行队列。下面,我们通过源码进一步论证。
下一个断点,查看下堆栈信息:
我们看到,调用了底层 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_flags
和 dq_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_s
, dispatch_queue_s
继承自 dispatch_object_s
;
看起来可能有点懵,给大家类比一下我们的class, class
继承自 objc_class
, objc_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_s
, dispatch_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)));
}
方法中的内容我们并不知道会走到哪一条分支,我们可以通过下符号点来判断一下。
代码执行来到_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
_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);
}
对于ctxt
和func
有操作的是 _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中打一个断点来做验证:
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
可以看到最终执行到的是 _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);
}
公布答案 :
>=
5<=
10000 因为:
-
第一,在while循环中,self.num 至少 ++ 5次之后,且num已经++完毕之后,才会跳出循环;这期间具体是添加了多少任务,就不得而知了,之后在打印的时候,那么至少也是5。
-
第二,for循环中,循环只管向异步中添加10000次任务,最后在打印的时候,不管这些任务是否全部执行完。
-
无论是同步还是异步去执行任务,在进行线程队列调度的时候,都会消耗一段时间。
这两道面试题,其实是在告诉我们,异步执行任务的时候,数据是不安全。
那么,如何才能保证我们的数据安全呢?这就需要在数据操作的时候根据情况加上锁
,关于锁的内容我们会在后面的篇章进行详细探索。
扩展
atomic
即使在 num 属性申明的时候使用 atomic 也是无法保证数据的安全,因为,atomic只能保证setter和getter方法的安全,对于多线程异步读取数据,也无能为力。