前言
在开发过程中,我们很多时候需要使用GCD
来处理业务流程。但是我们还是对队列
,任务
还是一知半解的状态,串行队列
和并发队列
区别,同步函数
和异步函数
,队列和函数的配合使用,GCD
下层封装等等,那么我们进行以下的分析。
1.GCD的相关概念
准备工作
1.1 GCD
GCD
全称是Grand Central Dispatch
,纯C
语⾔,提供了⾮常多强⼤的函数。
GCD
的优势所在
-
GCD
是苹果公司为多核
的并⾏运算
提出的解决⽅案 -
GCD
会⾃动利⽤更多的CPU
内核(⽐如双核、四核) -
GCD
会⾃动管理线程的⽣命周期
(创建线程、调度任务、销毁线程) - 程序员只需要告诉
GCD
想要执⾏什么任务,不需要编写任何线程管理代码
注意:GCD
将任务
添加到队列
,并指定执行任务的函数
。
1.2 任务
任务封装成block
,任务的block
没有参数也没有返回值。任务通过队列的调度,由线程来执行。
1.3 函数
执行任务的函数分为:异步函数
和同步函数
- 异步函数
dispatch_async
-
不⽤等待
当前语句执⾏完毕,就可以执⾏下⼀条语句 - 会开启线程执⾏
block
的任务 -
异步
是多线程
的代名词
-
- 同步函数
dispatch_sync
-
必须等待
当前语句执⾏完毕,才会执⾏下⼀条语句 不会开启线程
- 在当前线程执⾏
block
的任务
-
1.4 队列
队列分为两种:串行队列
和并发队列
。不同的队列中,任务排列的方式是不一样的,任务通过队列的调度
,由线程池
安排的线程来执行。
串行队列
和并发队列
都会遵循FIFO
的原则,即先进入先调度
的原则;任务的执行速度或者说执行时长,与各自任务的复杂度有关
。
-
串行队列
:通路比较窄,任务按照一定的顺序进行排列,一个一个执行
-
并发队列
:通道比较广,同一时间可能有多个任务执行
1.5 队列与函数
上面理解了队列
、函数
、任务
的区别,队列用来调用任务,函数用来执行任务。那么队列和函数不同的配合会有怎样的运行效果呢?
-
同步函数串行队列
-
不会开启线程
,在当前线程中执行任务 - 任务串行执行,任务一个接着一个执行
- 会产生阻塞
-
-
同步函数并发队列
-
不会开启线程
,在当前线程中执行任务 - 任务一个接着一个执行
-
-
异步函数串行队列
会开启一个线程
- 任务一个接着一个执行
-
异步函数并发队列
-
开启线程
,在当前线程执行任务 - 任务异步执行,
没有顺序
,CPU
调度有关
-
2. GCD相关案例分析
2.1 任务耗时分析
创建一个案例,分析任务的耗时,见下图:
1. 两次计时之间,什么也不做,只创建了一个串行队列,耗时
0.000001
秒,见下图:2.在主线程中执行
textMethod
,耗时时间有所增加,0.000142
秒,见下图:3.将任务放入一个
串行队列
,并同步执行该任务,耗时0.000117
秒。见下图:4.将任务放入
串行队列
,并异步执行
该任务,异步函数会开启一个线程
,并执行该任务。耗时0.000007秒5.经过这个案例分析,得出以下结论:
- 不管是采用什么方式,只要
执行任务都会耗时
-
异步执行是耗时相对较少
,异步可以用来解决我们在开发中的并发
、多线程
等问题
2.2 主队列添加同步任务
在当前的main
队列中添加一个任务,并同步执行
该任务会怎么样呢?见下图:
分析:
当前的流程中,默认队列就是
主队列
,也是一个串行队列
,任务执行的顺序是:
NSlog(@"0")
dispathc_sync任务块
-
NSlog(@"2")
而此时第二步中的块任务,会向当前的的主队列中添加一个任务NSlog(@"1")
。此时就产生相互等待
的问题,也就是我们常说的死锁
!见下图:
当前是一个串行队列
,dispathc_sync
任务块要执行完成需要执行NSlog(@"1")
,而NSlog(@"1")
需要等到NSlog(@"2")
执行完成后才能执行,而NSlog(@"2")
又要等dispathc_sync任务块
执行完成才会执行。这样主队列就会进入一个相互等待
状态,也就是死锁
!运行案例,见下图:
案例果然是出现了崩溃
,那么我们要怎么样解决呢?我们把主队列换成自定义的串行队列
看看:
完美解决了死锁的问题!
2.3 并发队列添加异步任务
以下是一个并发队列
,通道比较宽,所以这个嵌套过程并不会引起崩溃!见下图:
分析:
- 因为任务
复杂度
基本一致,所以打印顺序:1-5-2-4-3
; -
dispathc_async
会开启一个新的线程
去执行其中的任务;
运行案例查看结果:
结果跟我们上面分析的一样,如果将异步函数改成同步函数会怎样的?那我们现在就试试看。
分析:
因为
并发队列
,所以不会导致队列任务的阻塞,同时因为是同步执行
,所以不会开启新的线程,按照顺序去执行
流程。如下图:2.4 串行队列添加同步任务
创建一个串行队列
,并向该串行队列中添加同步任务3
,见下图:
分析:
该案例和
主队列添加同步任务
是一样的,主队列
也是串行队列
,所以我的猜想也是会崩溃
。从
任务2
开始进入一个串行队列
,dispathc_sync
任务块要执行完成需要执行NSlog(@"3")
,而NSlog(@"3")
需要等到NSlog(@"4")
执行完成后才能执行,而NSlog(@"4")
又要等dispathc_sync
任务块执行完成才会执行。这样串行队
列就会进入一个相互等待
状态,也就是死锁
!虽然此时是在子线程中
,但是队列通道遵循FIFO
原则,并且必须等待当前语句执⾏完毕,才会执⾏下⼀条语句。所以会崩溃
!
运行案例查看结果:
果然结果是会崩溃的!
2.5 并发队列多任务
并发队列中,添加同步任务和异步任务,如下图:
分析:
- 主队列中有
10
个任务 - 其中
任务3
和任务0
是同步任务
,所以任务3
一定在任务0
前面 -
任务1
、任务2
、任务3
的顺序是不确定
的 -
任务7
、任务8
、任务9
一定在任务0
的后面
3. 队列源码探索
查看汇编断点,发现队列的开辟实在dispatch_queue_create
方法实现的,而代码实现实在libdispatch.dyld
库实现的,见下图:
结论:
GCD
来自libdispatch.dylib
动态库。
3.1 主队列
3.1.1 dispatch_get_main_queue()定义
dispatch_get_main_queue ()分析
进入主队列定义的地方,如下图:
从上面的注释中我们可以发现一些内容:
- 主队列在应用程序中,用于
主程序
和main Runloop
- 主队列的创建在
main
函数之前即完成,也即是dyld
应用程序加载阶段即已创建
主队列是通过DISPATCH_GLOBAL_OBJECT
获取。全局搜索DISPATCH_GLOBAL_OBJECT
,该函数是通过宏进行定义的,定义如下:
#define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))
- 第一个参数:表示队列类型
- 第二个参数:主队列的对象,可以理解为通过
类型
和主队列对象
封装后得到main
队列
全局搜索_dispatch_main_q =
,找到了主队列初始化定义的地方,见下代码:
//默认是主串行队列,以上main_queue已经说明
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", //默认的线程label
//线程宽度为1,默认为1,用来区分串行队列和并行队列,并不是dq_serialnum
.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
.dq_serialnum = 1,
};
注意:线程宽度默认为1
,用来区分串行队列和并行队列
,并不是dq_serialnum
字段来区分
主队列是一个结构体
,并且继承自DISPATCH_GLOBAL_OBJECT_HEADER
,全局搜索DISPATCH_GLOBAL_OBJECT_HEADER
,其定义如下:
#if OS_OBJECT_HAVE_OBJC1
#define DISPATCH_GLOBAL_OBJECT_HEADER(name) \
.do_vtable = DISPATCH_VTABLE(name), \
._objc_isa = DISPATCH_OBJC_CLASS(name), \
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT, \
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT
#else
#define DISPATCH_GLOBAL_OBJECT_HEADER(name) \
.do_vtable = DISPATCH_VTABLE(name), \
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT, \
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT
#endif
dispatch_queue_static_s
结构体的定义
typedef struct dispatch_lane_s {
DISPATCH_LANE_CLASS_HEADER(lane);
/* 32bit hole on LP64 */
} DISPATCH_ATOMIC64_ALIGN *dispatch_lane_t;
// Cache aligned type for static queues (main queue, manager)
struct dispatch_queue_static_s {
struct dispatch_lane_s _as_dl[0]; \
DISPATCH_LANE_CLASS_HEADER(lane);
} DISPATCH_CACHELINE_ALIGN;
3.1.2通过lable
查找主队列定义
下面通过label
来定位主队列
。在主线程中进行debug
调试时,运行堆栈信息中,能看到下图的内容:
图中可以看到线程以及线程执行的任务栈信息,在主线程中,可以看到队列是一个
serial
类型,并且其对应的lable
是com.apple.main-thread
。
通过上面线索,分别打印自定义串行队列
、并发队列
、主队列
、全局队列
。见下图:
在创建自定义队列时,需要传入队列的
名称
,也就是队列lable
,以及队列的类型
。通过上面的打印信息,队列都有其对应的名称。根据队列的label
,在libdispatch.dylib
源码中进行全局搜索。如下:最终定位到的位置和
dispatch_get_main_queue()
分析时结果是一样的,主队列是一个结构体
,并且com.apple.main-thread
是默认label
。
3.1.3 主队列的初始化
前面查看主队列定义的注释时,已经说明在main
函数之前进行了初始化
,我们在研究dyld
时,分析objc_init()
初始化时得出过这样的结论:libSystem_init
-> libdispatch_init
-> objc_init
。那么主队列的初始化是否在dispatch_init()
方法中呢?
在
dispatch_init()
中成功找到了主队列初始化
的地方,获取默认队列,并将主队列地址绑定到当前队列和主线程中
。
总结:
主队列在main
函数之前,应用程序加载调用dispatch_init()
方法时,即完成创建。主队列为当前的默认队列,并绑定到主线程
中。
3.2 全局队列
3.2.1全局队列的定义
dispatch_get_global_queue(0, 0)
,进入全局队列定义的地方,如下:
创建全局并发队列时可以传参数,根据
不同服务质量
或者优先等级
提供不同的并发队列
。那么我们是不是可以想到:应该有一个全局的集合
,去维护这些并发队列。
3.2.1 查找维护的并发队列
全对队列label
(lable-com.apple.root.default
)搜索,得到如下:
总结:
系统会维护一个全局队列集合
,根据不同的服务质量
或者优先等级
提供不同的全局队列
。我们在开发工作中默认使用:dispatch_get_global_queue(0, 0)
。
3.3自定义队列的创建
由于libdispach.dylib
的源码不能编译,只能通过关键步骤逐步去定位
分析。在源码中全局搜索dispatch_queue_create
,结果很多,因为创建时第一个参数是一个常量,优化搜索条件,全局搜索dispatch_queue_create(const
,找到创建队列的入口,如下:
//队列入口方法
//label--name
//attr--队列类型
//DISPATCH_TARGET_QUEUE_DEFAULT=NULL 默认创建串行队列
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);
}
-
lable
为队列名称 -
attr
当前传入的,要创建的队列类型
- 调用
_dispatch_lane_create_with_target
方法,并传入默认队列类型
通过上图可以明确,默认队列被定义为NULL
。
定位_dispatch_lane_create_with_target方法
全局搜索_dispatch_lane_create_with_target(const
,定位到队列创建的地方,见下面源码:
// lable -> 名称
// dqa -> 要创建的类型
// tq -> 默认类型 - 串行NULL
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 创建 - dqa传入的属性串行还是并行
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
//
// Step 1: Normalize arguments (qos, overcommit, tq)
//
dispatch_qos_t qos = dqai.dqai_qos;
#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
_dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;
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 {
if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
// Serial queues default to overcommit!
overcommit = dqai.dqai_concurrent ?
_dispatch_queue_attr_overcommit_disabled :
_dispatch_queue_attr_overcommit_enabled;
}
}
if (!tq) {
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");
}
}
//
// Step 2: Initialize the queue
//
if (legacy) {
// if any of these attributes is specified, use non legacy classes
if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {
legacy = false;
}
}
// 类的类型
const void *vtable; // - 设置类 类是通过宏定义拼接而成
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
if (dqai.dqai_concurrent) {
// OS_dispatch_##name##_class
// OS_dispatch_queue_concurrent - 宏定义拼接类类型
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
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);
if (tmp != label) {
dqf |= DQF_LABEL_NEEDS_FREE;
label = tmp;
}
}
// dq创建的地方 - alloc init 队列也是个对象
// OS_dispatch_queue_serial
// OS_dispatch_queue_concurrent
// 即然是对象应该有ISA的走向
dispatch_lane_t dq = _dispatch_object_alloc(vtable, // 类
sizeof(struct dispatch_lane_s)); // alloc
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ? // 区分串行和并发队列
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // init
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; // 通过这层可以理解为经过一层封装后,返回的依然是dq
}
分析:
该方法的返回值为dq
,虽然调用了_dispatch_trace_queue_create
方法对dq
进行了封装处理,但是最终返回的依然是dq
,所以dq
才是我们的研究重点!,以下是提取出dq的创建流程代码:
// dq创建方 - alloc init 队列也是个对象
// OS_dispatch_queue_serial
// OS_dispatch_queue_concurrent
// 即然是对象应该有ISA的走向
dispatch_lane_t dq = _dispatch_object_alloc(vtable, // 类
sizeof(struct dispatch_lane_s)); // alloc
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ? // 区分串行和并发队列
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // init
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__);
进入_dispatch_object_alloc方法
//类似于对象的初始化,vtable就是要初始化的对象,size就是申请内存的大小
void *
_dispatch_object_alloc(const void *vtable, size_t size)
{
#if OS_OBJECT_HAVE_OBJC1
const struct dispatch_object_vtable_s *_vtable = vtable;
dispatch_object_t dou;
dou._os_obj = _os_object_alloc_realized(_vtable->_os_obj_objc_isa, size);
dou._do->do_vtable = vtable;
return dou._do;
#else
//开辟内存,类对象是vtable
return _os_object_alloc_realized(vtable, size);
#endif
}
然后调用_os_object_alloc_realized
方法,完成初始化流程。在该函数中,通过调用calloc
进行初始化,同时isa
指向cls
,也就是vtable
。
inline _os_object_t
_os_object_alloc_realized(const void *cls, size_t size)
{
_os_object_t obj;
dispatch_assert(size >= sizeof(struct _os_object_s));
while (unlikely(!(obj = calloc(1u, size)))) {
_dispatch_temporary_resource_shortage();
}
obj->os_obj_isa = cls;
return obj;
}
- 调用
calloc
进行初始化。 - 并将
isa
指向cls
,也就是vtable
。 -
vtable
是队列的根类
。
解析dqai.dqai_concurrent ? DISPATCH_QUEUE_WIDTH_MAX : 1
不难理解,判断是否为并发队列
,如果是,传入DISPATCH_QUEUE_WIDTH_MAX
,否则传入1
。也就是说,串行队列
这里传入1
,如果是并发队列
,则传入DISPATCH_QUEUE_WIDTH_MAX
。其定义见下图:
进入_dispatch_queue_init初始化
进入_dispatch_queue_init
构造函数的实现,发现DQF_WIDTH(width);
也就是用来确定队列的类型
,以此来区分串行队列和并发队列
,见下图:
到这里我们也就可以确定主队列的类型,在主队列的结构体定义中,DQF_WIDTH(1);,所以主队列是串行队列。
结论:_dispatch_queue_init
方法是对dq的属性的设置,如dq_label
、dq_priority
等
dqai的初始化
dp
初始化过程中传入的参数vtable
、dqai
、dqf
,分别是什么,又起到什么作用呢?我们继续分析。
// dqai 创建 - dqa传入的属性串行还是并行
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
在_dispatch_lane_create_with_target
第一行,就进行了dqai
的初始化,其中参数dqa
为要创建的队列的类型。查看_dispatch_queue_attr_to_info
源码:
初始化了
dqai
,并判断dqa
的类型,如果是并发队列
,则设置并发队列为true
,否则默认为串行队列
。在调用_dispatch_queue_ini
t对dq
进行构造时,对队列类型进行了区分,也就是DQF_WIDTH(width);
的传参,串行队列width=1
,否则为并发队列。
vtable是什么
vtable
可以理解为是一个类,或者说构造队列的模板类
。vtable
初始化流程见下面源码:
const void *vtable; // - 设置类 类是通过宏定义拼接而成
if (dqai.dqai_concurrent) {
// OS_dispatch_##name##_class
// OS_dispatch_queue_concurrent - 宏定义拼接类类型
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
dqai
在前面创建时会区分队列的类型
,根据队列的类型来初始化不同的vtable
。DISPATCH_VTABLE
是一个宏定义的方法,全局搜索DISPATCH_VTABLE
的定义,最终找到以下完整的定义流程:
// DISPATCH_VTABLE定义
#define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)
// vtable symbols - 模板
#define DISPATCH_OBJC_CLASS(name) (&DISPATCH_CLASS_SYMBOL(name))
// 拼接形成类
#define DISPATCH_CLASS_SYMBOL(name) OS_dispatch_##name##_class
DISPATCH_VTABLE函数的传参根据不同的队列类型
传参不一致。
-
并发队列
传参queue_concurrent
,最终拼接后,队列类型,也就是对应的类为:OS_dispatch_queue_concurrent
- 串行队列
传参queue_serial
,最终拼接后,队列类型,也就是对应的类为:OS_dispatch_queue_serial
所以vtable
对应的就是队列的类型。通过拼接完成类的定义
,这和我们在应用层使用的队列类型是一致的,见下图:
总结:
在底层,会根据上层传入的队列名称lable
和队列类型
进程封装处理。根据类型初始化对应的vtable
,也就是对应的队列类
(模板类
)。通过alloc
和init
方法完成队列的内存开辟
和构造初始化
过程,设置队列的对象的isa
指向,并完成队列类型的区分。
4. 函数执行
上面已经总结了GCD中的异步函数与同步函数,如下:
- 同步函数
dispatch_sync
- 异步函数
dispatch_async
疑问:那么他们的底层执行逻辑是什么呢?这个模块主要探究这个哦
4.1同步函数
在libdispatch.dylib
源码中,全局搜索dispatch_sync(dis
,找到了同步函数的入口,如下:
//两个参数分别是队列和任务
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);
}
两个参数分别是:队列dq
和任务work
。继续跟踪,调用_dispatch_sync_f
函数,那么进入_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
_dispatch_sync_f
_dispatch_sync_f_inline
参数:
-
dq
:队列dq
-
ctxt
:任务work
-
func
:宏定义的函数_dispatch_Block_invoke
#define _dispatch_Block_invoke(bb) \ ((dispatch_function_t)((struct Block_layout *)bb)->invoke)
在_dispatch_sync_f_inline
方法中,哪一行才是我们需要研究的重点呢?先简单分析一下,首先最前面的一段判断流程:
if (likely(dq->dq_width == 1)) {
return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
}
此部分显然是串行队列
流程,因为上面已经分析了,dq_width=1
时,表示串行队列
。那么_dispatch_sync_f_slow
和_dispatch_sync_recurse
会总哪一步呢?我们可以通过下符号断点来确定,通过下符号断点,发现自定义并发队列,其成功走到_dispatch_sync_f_slow
中。见下图:
进入
_dispatch_sync_f_slow
方法,继续下符号断点如下:显然传入的地方不太可能是
null
,所以大概率会走_dispatch_sync_function_invoke
方法,同样采用下符号断点的方式跟踪。见下图:进入
_dispatch_sync_function_invoke
方法后,会调用_dispatch_sync_function_invoke_inline
方法,见下图:进入_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);
}
这就是我们上面说的func
和ctxt
(work
任务)对应的函数入口。
- 函数入口:
_dispatch_client_callout
上面的方法有很多哦,我们运行以下案例,bt
看以下函数调用栈信息,如下:
发现
_dispatch_client_callout
方法最终完成了任务的调用执行
,进入_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_client_callout
方法有很多,但是最终都通过func
完成了work
的调用执行。
总结:
在同步流程中通过宏定义的函数_dispatch_Block_invoke
,也就是func
,完成任务work
的执行。
4.2 异步函数
在libdispatch.dylib
源码中,全局搜索dispatch_async(dis
,找到了异步函数的入口。见下:
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;
//任务包装器:接受->保存->函数式编程->保存任务->block
//保存block,把work(任务)包装成func保存到dc -- 任务的封装
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
//并发队列处理
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
4.2.1任务的封装_dispatch_continuation_init
在该方法里,对work
进行了封装处理,其中方法_dispatch_continuation_init
可以理解为一个任务包装器
,进入_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)
{
//对work完成一次copy操作 -->ctxt
void *ctxt = _dispatch_Block_copy(work);
//两个路径都是对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);
}
//将work包装成一个func
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_Block_invoke
这个函数很熟悉,也就是同步函数中的func
。但是这里有一个判断,见下面代码:
dispatch_function_t func = _dispatch_Block_invoke(work);
if (dc_flags & DC_FLAG_CONSUME) {
func = _dispatch_call_block_and_release;
}
dc_flags
的初始值就是DC_FLAG_CONSUME
,所以&
处理后,这里会进入条件语句中,并且func
被重新设置为_dispatch_call_block_and_release
。
进入_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; //ctxt = work
// 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);
}
在这里对将work
和func
封装到dc
中,同时对任务的优先级进行处理
,因为异步函数由于线程或者cpu
的调用是异步
的,所以这里会对任务的调用优先级进行处理。
回到dispatch_async
方法实现,完成任务包装后,调用_dispatch_continuation_async
方法进行异步函数的处理流程。如下:
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_push
函数三个参数分别为:队列
、dc
(包装的任务)、qos
。查看dx_push
的宏定义如下:
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
vtable
就是上面在分析队列的创建初始化时的模板类
,也就是队列
对应的类。这里会调用vtable
这个类的dq_push
方法,直接搜索dq_push
,找到其实现:
底层为
不同类型的队列提供不同的调用入口
,比如全局并发队列
会调用_dispatch_root_queue_push
方法。依次作为入口,全局搜索_dispatch_root_queue_push
方法的实现:在此流程中,前面只是做一些判断封装处理,最终会走到最后一行代码
_dispatch_root_queue_push_inline
中,继续跟踪源码流程:在
_dispatch_root_queue_push_inline
中调用了_dispatch_root_queue_poke
方法,_dispatch_root_queue_poke
中的核心流程为_dispatch_root_queue_poke_slow
,见下图所示:进入
_dispatch_root_queue_poke_slow
的实现,发现了_dispatch_root_queues_init()方法
,如下图:进入
_dispatch_root_queues_init()
方法,在该方法中采用的单例
处理,见下图:单例处理
_dispatch_root_queues_init_once
里面做了什么呢?跟着进去看看:在该方法中进行了
线程池的初始化处理
、工作队列的配置
、工作队列的初始化
等工作,这也就是解释了为什么_dispatch_root_queues_init_once
是单例的。单例可以避免重复的初始化
。同时这里有一个关键的设置,执行函数的设置,也就是将
任务执行的函数被统一设置成了_dispatch_worker_thread2
,如下代码:
cfg.workq_cb = _dispatch_worker_thread2;
通过bt
打印运行堆栈信息,来验证异步函数最终任务是通过_dispatch_worker_thread2
调用的。见下图所示:
总结:
通过跟踪异步处理流程,系统针对不同的队列类型
,执行不同的dq_push
的方法,并通过单例
的形式完成了线程池的初始化
、工作队列的配置
等工作,并且底层最终通过_dispatch_worker_thread2
完成了异步函数中任务
的调用执行。
总结
其实GCD
还有很多底层原理这篇文章没有涉及到,如对于异步函数,线程在哪里开辟
?底层通过_dispatch_worker_thread2
方法完成任务的执行,那么触发调用的位置在哪
?GCD中单例的逻辑是怎样的
?我们下篇文章继续探索看看。