一、什么是GCD
GCD全称是 Grand Central Dispatch, 纯C语言编写的API,提供了非常多的强大的函数。
GCD的优势:
- CGD是苹果公司为多核的并行运算提出的解决方案
- CGD会自动利用更多的CPU内核
- GCD会自动管理线程的生命周期
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理的代码
二、GCD的使用
看一下最基本的使用,来写一个简单的异步队列GCD
dispatch_block_t block = ^{
NSLog(@"hello world");
};
dispatch_queue_t queue = dispatch_queue_create("com.test", NULL);
dispatch_async(queue, block);
有的朋友可能会讲在queue中应该写 DISPATCH_QUEUE_SERIAL , 但是我们点进去就知道了。 DISPATCH_QUEUE_SERIAL这个宏其实就是NULL, 为了日后的记忆方便,我个人通常都写为NULL。
一般来调用GCD都会分为异步调用和同步调用:
-
异步 dispatch_async :
- 不用等待当前语句执行完毕,就可以执行下一条语句。
- 它会开启线程执行block的任务
- 异步实际上就是多线程的代名词。
-
同步dispatch_sync :
- 必须等待当前语句执行完毕,才会执行下一条语句
- 不会开启线程
- 在当前执行block的任务
还有执行任务的串行与并发的方式:
- 串行队列: 遵循First in first out原则, 多个任务会按照队列的结构依次执行。
- 并发队列: 多个任务同时进行,会导致乱序效果。任务消耗的时间与优先级、任务复杂度等有关系。
2.1、队列和函数的搭配
2.1.1、同步串行队列
特性:
- 意味着不会开启线程,在当前线程执行任务
- 任务按照队列结构一个接着一个
- 会产生堵塞
代码示例:
dispatch_queue_t queue = dispatch_queue_create("com.test1", NULL);
dispatch_sync(queue, ^{
NSLog(@"1");
});
NSLog(@"2");
此时因为线程堵塞必须等待打印1后才会打印2.
2.1.2、异步串行队列
特性:
- 开启一条新的线程
- 任务按照队列结构一个接着一个
- 不会产生堵塞
代码示例:
dispatch_queue_t queue = dispatch_queue_create("com.test2", NULL);
dispatch_async(queue, ^{
NSLog(@"1");
});
NSLog(@"2");
当程序开辟线程后,并不会等待打印1, 而是直接进入下一步打印2. 当打印2完成后,GCD内部的一些配置工作等完成,进行打印1.
注意: 因为GCD的开启需要一点点时间,所以相对于系统直接打印2的时间要长一点,所以并不会先打印1再打印2
2.1.3、同步并发队列
特性:
- 不会开启线程,在当前线程完成
- 任务按照队列结构一个接着一个
- 会产生堵塞 代码示例:
dispatch_queue_t queue = dispatch_queue_create("com.test3", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"1");
});
NSLog(@"2");
因为是同步队列, 所以必然会造成堵塞。
2.1.3、异步并发队列
特性:
- 开启线程,在当前线程执行任务
- 任务异步执行,没有顺序,与CPU调度有关
- 不会产生堵塞
代码实现:
dispatch_queue_t queue = dispatch_queue_create("com.test4", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1");
});
NSLog(@"2");
2.2、复合调用
看第一段示例代码:
dispatch_queue_t queue = dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
分析:
- 这是一个并发
- 首先会打印1
- 进入异步函数,此时不会堵塞,应该会继续向下执行,打印5
- 此时异步任务执行,打印2
- 异步任务里又有一个异步,此时会不会堵塞,继续向下,打印4
- 最后执行最里面的异步任务, 打印3
最终的打印结果就是 1 5 2 4 3
第二段代码:
dispatch_queue_t queue = dispatch_queue_create("com.test", 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");
});
// A: 1230789
// B: 1237890
// C: 3120798
// D: 2137890
分析:
- 首先是连续两个异步任务,紧接着就是一个同步任务。也就意味着 1 2 3 打印的顺序不是固定的。并且1 2 的任务复杂明显的很小,所以在 3 0 后面打印的几率几乎为0
- 同步任务后面是 0 , 也就是必须等3打印完才会打印0, 所以3一定在0之前打印
- 0后面是连续三个异步函数,也就是说必须等到0打印完后才会执行。 所以 0一定在 7 8 9之前打印
- 7 8 9 的顺序是无序的
综上分析,答案应该是AC
第三段代码:
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
// 异步函数
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
分析:
- 串行队列会堵塞
- 打印1,碰到异步, 打印5
- 打印2, 碰到同步任务,把同步任务放进队列任务。把打印3放进队列任务
- 在执行同步任务的时候,这个同步任务又将打印3放进任务队列, 此时这个同步任务必须要完成打印3才会继续向下执行。但是这个打印3又排在打印4任务的后面。会造成同步任务等待打印3执行才会执行打印4, 但是打印3又在等待打印4的执行。 这就形成一个很经典的问题: 死锁。

所以最终的打印结果应该是1 5 2
三、GCD的具体实现
还是打开熟悉的oc750源码。 进入 dispatch_queue_create() :



原来它在 libdispatch.dylib 这个里面。
进入官网下载一份libdispatch :
根据我们调用 dispatch_queue_create(), 第一个参数为const char *, 所以在 libdispatch中搜索 dispatch_queue_create(const char, 找到方法实现:
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(const ,来到具体实现 :
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
//省略
}
3.1、线程的创建
首先我们来看一下是如何实现串行和并发的, 也就是说第二个参数就并发与串行的设置。这函数里第一行就是与dqa相关,所以我们的重点应该就是在 _dispatch_queue_attr_to_info 里面。
全局搜索 _dispatch_queue_attr_to_info(dis :
dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
//定义一个结构体
dispatch_queue_attr_info_t dqai = { };
//串行队列传递过来的是NULL,意味着返回过去的dqai是空的
if (!dqa) return dqai;
//....省略
//....位域处理省略
dqai.dqai_concurrent = !(idx % DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT);
idx /= DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT;
//....省略
return dqai;
}
在串行队列的情况下,返回的 dqai 是一个空的结构体。
下面对 dqai.dqai_concurrent 进行了处理,也就意味着当是并发队列的时候,dqai.dqai_concurrent 里面是有值的。
来到 _dispatch_lane_create_with_target 下文中:
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
//...省略
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 (dqai.dqai_concurrent) {
// 通过dqai.dqai_concurrent 来区分并发和串行
// OS_dispatch_queue_concurrent_class
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
//...省略
// 开辟内存 - 生成响应的对象 queue
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));
// 构造方法
_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;
}
这里做了判断, overcommit 记录是否是并发的开关, vtable 决定了是并发任务还是串行任务。
来写一个串行一个并发的打印出来看一下:

在构造方法哪里我们看到的:
dqai.dqai_concurrent ? DISPATCH_QUEUE_WIDTH_MAX : 1
这个width貌似很适合这里,那么再去看一下 DISPATCH_QUEUE_WIDTH_MAX:

0x1000 - 2 = 0xffe 表示并发队列需要记录这个with的最大值。
这个width不能满,起码要留一些空间。猜测这个东西很有可能是所谓的线程池。
在target中,有overcommit标记就是串行队列,没有overcommit就是并发队列。
在上面代码里,有一句 dq->do_targetq = tq; ,这个target是dq传递过来的。向上查到到一行tq相关代码:
#define DISPATCH_QOS_UNSPECIFIED ((dispatch_qos_t)0)
#define DISPATCH_QOS_MAINTENANCE ((dispatch_qos_t)1)
#define DISPATCH_QOS_BACKGROUND ((dispatch_qos_t)2)
#define DISPATCH_QOS_UTILITY ((dispatch_qos_t)3)
#define DISPATCH_QOS_DEFAULT ((dispatch_qos_t)4)
#define DISPATCH_QOS_USER_INITIATED ((dispatch_qos_t)5)
#define DISPATCH_QOS_USER_INTERACTIVE ((dispatch_qos_t)6)
#define DISPATCH_QOS_MIN DISPATCH_QOS_MAINTENANCE
#define DISPATCH_QOS_MAX DISPATCH_QOS_USER_INTERACTIVE
#define DISPATCH_QOS_SATURATED ((dispatch_qos_t)15)
tq = _dispatch_get_root_queue(
qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
在创建GCD的时候,并没有添加qos相关的参数,那么qos在这个时候一定是空的, 也就是表示 _dispatch_get_root_queue 第一个参数正常情况下是 DISPATCH_QOS_DEFAULT 为 4, qos为4或者0,后面的 overcommit 判断是不是串行,结果应该为0或1
进入 _dispatch_get_root_queue :
_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");
}
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}
那么这里的_dispatch_root_queues[]就有一种情况。 2 *(4 - 1) + 0或者1 = 6或7
来看一下 _dispatch_root_queues 的结构:
struct dispatch_queue_global_s _dispatch_root_queues[] = {
//...省略
_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_label就是我们创建两个GCD所看到的 "com.apple.root.default-qos" 和 "com.apple.root.default-qos.overcommit"。
从这里就知道,我们创建的GCD线程都应该是根据root_queue这个模板来进行构建的,除了mian_queue。
这些代码是从libdispatch 中看到的,也就是说当libdispatch 启动的时候就会加载这些东西,包括qos。程序的加载肯定会加载object相关了,所以可以从object_init()这个位置看一下。 在_os_object_init();下面有一个 _dispatch_introspection_init();
进入 _dispatch_introspection_init(); , 看到有一段
for (size_t i = 0; i < DISPATCH_ROOT_QUEUE_COUNT; i++) {
_dispatch_trace_queue_create(&_dispatch_root_queues[i]);
}
表示在一个一个的去初始化,创建线程。
下面有一行 _dispatch_trace_queue_create(&_dispatch_main_q); 表示着主线程队列的创建。我们在创建一个dispatch_get_main_queue,这个函数的实现实际上就是查找 _dispatch_main_q。
我们除了用串行并发,还会使用到主队列与全局并发队列
用代码来打印一下主队列与全局并发队列:

先来看一下主队列,全局搜索一下 com.apple.main-thread:

在global里看到width = 0xfff。 0xffe给了我们自定义的并发队列,0xfff给了全局并发队列。