0%

GCD底层原理-1

GCD简介

全称是 Grand Central Dispatch。底层为C语言,将任务添加到队列,并且指定执行任务的函数。GCD提供了非常强大的函数。

GCD的优势

  • 是苹果公司为多核的并行运算提出的解决方案
  • 会自动利用更多的CPU内核(比如双核、四核)
  • 会自动管理线程的生命周期(创建线程、调度任务、销毁线程) 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

同步和异步函数

GCD使用block封装任务,任务的block没有参数也没有返回值。

任务的调度有同步和异步之分。

同步dispatch_sync

  1. 必须等待当前语句执行完毕,才会执行下一条语句。
  2. 同步不会开启线程。
  3. 在当前线程执行block任务。

异步 dispatch_async

  1. 会开启新线程执行block任务
  2. 异步是多线程的代名词

队列

队列是一种数据结构,遵循先进先出的原则(FIFO)。分为串行队列和并发队列(并行)。

不管是串行还是并发队列,谁在队列的最前头谁先开始执行。但是执行的快慢与当前所需资源有关。

串行等待上一个任务执行完成
并发不会等待上一个任务执行完成

函数与队列

同步函数 + 串行队列

  • 不会开启线程,在当前线程执行任务
  • 执行完一个执行下一个,会产生堵塞
1
2
3
4
5
6
7
8
9
10
11
12
// 创建串行队列
- (void)serialSyncTest{
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 20; i++) {
// a
dispatch_sync(queue, ^{
// b
NSLog(@"i = %d, thread = %@",i,[NSThread currentThread]);
});
// c
}
}

会从0到19,按照a-b-c的顺序输出所有数据。根据打印的线程,发现就是主线程,并不会开启新线程。

同步函数 + 并发队列

  • 不会开启线程,在当前线程执行任务
  • 任务一个接着一个执行
1
2
3
4
5
6
7
8
9
10
11
- (void)concurrentSyncTest{

//1:创建并发队列
dispatch_queue_t queue = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<20; i++) {
dispatch_sync(queue, ^{
NSLog(@"i = %d, thread = %@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}

同步并发队列,会按照顺序执行,最后打印hello queue

同步函数的情况下,不管是串行还是并发,都不会开启新线程,任务按步执行。

异步函数 + 串行队列

  • 开启新线程
  • 任务一个接一个执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 异步串行
- (void)serialAsyncTest{
//1:创建串行队列
dispatch_queue_t queue = dispatch_queue_create("queue3", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<20; i++) {
// a
dispatch_async(queue, ^{
// b
NSLog(@"i = %d,thread = %@",i,[NSThread currentThread]);
});
// c
}
// d
NSLog(@"hello queue");
}

由于是异步串行队列,线程的创建会有耗时操作,在for循环中执行的顺序是a-c-b(a),执行了a之后是c,在之后不一定是a还是b。

而d语句可能先执行,也可能后执行。

根据线程的打印情况,发现会开启新线程。

异步函数 + 并发队列

  • 开启新线程,并开始执行
  • 任务异步执行,没有顺序,与CPU调度有关
1
2
3
4
5
6
7
8
9
10
- (void)concurrentAsyncTest{
//1:创建并发队列
dispatch_queue_t queue = dispatch_queue_create("queue4", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<20; i++) {
dispatch_async(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}

会开启新的线程,执行没有顺序。

函数队列的面试题

异步并发队列

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)textDemo {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
// 耗时
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}

这是一个并行队列,内部是异步执行。当block内部有任务需要执行时,会产生耗时,所以就会先执行完成block外部的简单调用。而在block内部,是按照正常的流程执行的。

打印的结果是1 5 2 4 3

异步串行队列

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)textDemo1{
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}

只要是block内部需要执行,则一定是耗时操作,所以先执行1,然后5,在异步串行队列内部,是与外部一样的道理,

结果是:1 5 2 4 3

并发队列 异步同步嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)textDemo3 {
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}

结果为 1 5 2 3 4
同步并发队列也不会开启新线程,一个一个执行。

串行 异步同步嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)textDemo4 {
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
NSLog(@"1");
// 异步函数
dispatch_async(queue, ^{
NSLog(@"2");
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}

上面已经有过异步串行、异步并行的例子了,只要异步函数内部有任务要执行,就属于耗时操作,会优先执行完毕外部的简单操作。所以先执行 1 5

在异步函数内部,继续串行执行。这时候会执行2,然后碰到了同步串行队列。而同步串行队列是需要等待外部执行完成之后才会执行,但是4也在等待同步函数的执行,造成了互相等待,发生了死锁。

这里即使把4注释掉,也同样会发生死锁。

所以结果为: 1 5 2 – 死锁

总结

我们一定要清楚,不管是异步还是同步,都是对与block块和其下一行代码来说的。在block内部不管当前是异步还是同步,串行还是并行,都是从上往下执行的。

另外并发和串行的区别:

  • 并发不会等待一个任务执行完成才执行。
  • 串行会等待一个任务执行完毕才执行。

同步和异步:
同步和异步是对当前线程而言的。

  • 异步函数下,不管是串行队列还是并行队列,都不影响block块之外的内存执。因为block内部是在新开启的线程中执行的。
  • 同步函数下,并行队列不受影响,因为并行不需要等待上一个任务执行完成。如果是串行队列,那在当前线程下会发生死锁。

主队列 & 全局队列

1
2
3
4
5
6
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t conque = dispatch_queue_create("conque", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);

NSLog(@"%@\n%@\n%@\n%@",serial,conque,mainQueue,globQueue);

打印一些这4个队列:

1
2
3
4
<OS_dispatch_queue_serial: serial>
<OS_dispatch_queue_concurrent: conque>
<OS_dispatch_queue_main: com.apple.main-thread>
<OS_dispatch_queue_global: com.apple.root.default-qos>

这里打印了4个队列,但是其实一共只有两个队列,就是串行队列和并发队列。

通过汇编手法,我们发现GCD的源码存在与libdispatch.dylib库中,我们就从这个库里看GCD的底层实现。

主队列

dispatch_get_main_queue()主队列专门用来在主线程上调度任务的串行队列,并不会开启新线程。

如果当前主线程正在执行任务,那么无论主队列中被添加了什么任务,都不会被调度执行。

1
dispatch_queue_t mainQueue = dispatch_get_main_queue();

我们通过源码查看

1
2
3
4
5
6
//
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}

dispatch_get_main_queue的解释中,我们发现:主队列依赖于主线程dispatch_main()runloop,并且主线程是在main()函数之前自动创建的(dyld的流程)。

先看看啥是dispatch_queue_main_t

A dispatch queue that is bound to the app’s main thread and executes tasks serially on that thread.

1
typedef struct dispatch_queue_static_s *dispatch_queue_main_t;

可以看出来OS_dispatch_queue_main是一个类。

那我们找一找DISPATCH_GLOBAL_OBJECT这个的实现:

1
#define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))

这是一个宏定义,内部使用的是一个type类型强转之后与object进行二进制的”&“运算。

然后看看这两个参数:

dispatch_queue_main_t

The type of the default queue that is bound to the main thread
从字面意思就是把默认线程绑定到主线程。

_dispatch_main_q

Returns the default queue that is bound to the main thread.
返回一个绑定了主线程的默认线程。接下来我们通过源码看一下_dispatch_main_q

1
2
3
4
5
6
7
8
9
10
11
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,
};

_dispatch_main_q是一个结构体:

dq_label:使用的标签,上方代码中打印出来的东西。
dq_atomic_flags:是一个flag,DQF_WIDTH(1)表示宽度,1只能通过1个
dq_serialnum:串行数是1

知道了两个参数,我们直接使用”&“运算看是否能得到我们想要的主线程。

1
dispatch_queue_t mainQueue = (OS_OBJECT_BRIDGE dispatch_queue_main_t)&(_dispatch_main_q);

得到的这个mainQueue与上方的点结果是一直到。

接下来我们得验证一下,dispatch_get_main_queue是在main函数之前执行的。在dyld的流程中,我们知道他会执行一个libdispatch_init(void)的操作。在它的内部源码中有如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void
libdispatch_init(void)
{
// ...
// line 7921
#if DISPATCH_USE_RESOLVERS // rdar://problem/8541707
_dispatch_main_q.do_targetq = _dispatch_get_default_queue(true);
#endif
// 设置当前线程
_dispatch_queue_set_current(&_dispatch_main_q);
// 绑定线程
_dispatch_queue_set_bound_thread(&_dispatch_main_q);
// ...
}

在第7758行代码,可以看到创建了 _dispatch_main_q静态结构体,之后设置当前线程为为主线程,然后进行绑定。

全局队列

dispatch_get_global_queue(0,0),为了方便使用,苹果创建了全局队列,全局队列是一个并发队列

在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列。

1
2
dispatch_queue_global_t
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);

这里有两个参数:
第一个identifier:表示优先级,与QOS的优先级一一对应。

1
2
3
4
DISPATCH_QUEUE_PRIORITY_HIGH        // 高
DISPATCH_QUEUE_PRIORITY_DEFAULT // 默认
DISPATCH_QUEUE_PRIORITY_LOW // 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND // BACKGROUND

第二个参数是flag:
保留供将来使用的标志。始终将此参数指定为0。

接下来,查看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dispatch_queue_global_t
dispatch_get_global_queue(long priority, unsigned long flags)
{
dispatch_assert(countof(_dispatch_root_queues) ==
DISPATCH_ROOT_QUEUE_COUNT);

if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {
return DISPATCH_BAD_INPUT;
}
dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);
#if !HAVE_PTHREAD_WORKQUEUE_QOS
if (qos == QOS_CLASS_MAINTENANCE) {
qos = DISPATCH_QOS_BACKGROUND;
} else if (qos == QOS_CLASS_USER_INTERACTIVE) {
qos = DISPATCH_QOS_USER_INITIATED;
}
#endif
if (qos == DISPATCH_QOS_UNSPECIFIED) {
return DISPATCH_BAD_INPUT;
}
return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
}

这一坨东西其实都不用看,只需要看到最后return _dispatch_get_root_queue()是这么个东西。

1
2
3
4
5
6
7
8
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");
}
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}

_dispatch_root_queues[]应该就是一个数组,通过传进来的参数获取对应的queue。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
struct dispatch_queue_global_s _dispatch_root_queues[] = {
// ...
.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
_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,
),
};

我们看到了lable的内容,有我们刚才打印的那个com.apple.root.default-qos。会根据我们设置的优先级返回不同的全局队列。

1
2
3
4
dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL)

#define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)

dq_atomic_flags的值也就是 (0x1000 - 1) = 4095

dispatch_queue_create 原理

直奔主题,在源码中查看dispatch_queue_create方法:

1
2
3
4
5
6
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_QUEUE_SERIAL

我们看一下源码:

1
#define DISPATCH_QUEUE_SERIAL NULL

所以,通常情况下,我们在创建串行队列时,也会使用NULL来替换。

并发:DISPATCH_QUEUE_CONCURRENT

我们看一下源码实现:

1
2
3
#define DISPATCH_QUEUE_CONCURRENT \
DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, \
_dispatch_queue_attr_concurrent)

这里有一个DISPATCH_GLOBAL_OBJECT()函数,在主队列中已经介绍过了(通过&运算)。

_dispatch_lane_create_with_target这个函数中,我们发现很长很难懂,那我们就通过多年的编程经验,看它返回的时候一个什么东西,然后看这个是怎么创建的。下面的代码是经过删减的,有需要的自行查看源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
// 1. 创建 dqai
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
// ...
// 2. 创建vtable
const void *vtable;
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
if (dqai.dqai_concurrent) {
// OS_dispatch_queue_concurrent
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
// ...
// 3. label赋值
if (label) {
const char *tmp = _dispatch_strdup_if_mutable(label);
if (tmp != label) {
dqf |= DQF_LABEL_NEEDS_FREE;
label = tmp;
}
}
// 4. dq alloc分配内存空间
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));
// 5. dq init操作
_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
// 6. 对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) {
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;
}

创建dqai

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
dispatch_queue_attr_info_t dqai = { };

if (!dqa) return dqai;

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

还记得我们在创建队列时传的参数吗?第一个是label,第二个是串行还是并发。

这个dqai会判断当前是串行还是并发,并对dqai.dqai_concurrent = true;进行赋值。

创建 vtable

vtable会根据当前是串行还是并发进行创建,我们一步一步的追寻vtable是什么。

1
2
3
4
5
6
7
8
#if OS_OBJECT_HAVE_OBJC2
#define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)
#define DISPATCH_OBJC_CLASS(name) (&DISPATCH_CLASS_SYMBOL(name))
#define DISPATCH_CLASS_SYMBOL(name) OS_dispatch_##name##_class
#elif
...
#end

1
2
3
4
5
6
if (dqai.dqai_concurrent) {
// OS_dispatch_queue_concurrent
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}

通过源码发现,vtable就是一个类。最后生成的就是OS_dispatch_##name##_class

##name##就是创建vtable时的参数,就会生成对应的OS_dispatch_queue_serial_classOS_dispatch_queue_concurrent_class

label赋值

这个就是创建时传入的那个label标签的内容。

dq alloc分配内存空间

这里执行了alloc操作,开始分配内存空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));


void *
_dispatch_object_alloc(const void *vtable, size_t size)
{
// 这个是在mac下执行
#if OS_OBJECT_HAVE_OBJC1
...
#else
// 这里分配内存,isa指向
return _os_object_alloc_realized(vtable, size);
#endif
}

真正的alloc操作是在这里执行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)))) {
// 执行的都是likely的操作,所以不会走这里,这里也没有意义,内部是sleep操作
_dispatch_temporary_resource_shortage();
}
// isa指向
obj->os_obj_isa = cls;
return obj;
}

dq init操作

alloc之后,执行init操作。

初始化的时候会判断当前要生成并发还是串行队列,并发的话,个数是DISPATCH_QUEUE_WIDTH_MAX,串行是1,就是开辟的最大任务数。

对dq进行赋值

比如lable标签、overcommit,priority等赋值。同时绑定target。

最后return

1
return _dispatch_trace_queue_create(dq)._dq;

这里看源码都是最后返回的都是dq对应的数据。

dispatch_async 源码

我们接下来看一下异步并发队列函数的源码。

1
2
3
dispatch_async(conque, ^{
NSLog(@"12334");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
void
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

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;
// 任务包装器,只有这里有对work的操作
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}

dispatch_async()函数内部会执行_dispatch_continuation_init,这个是函数中的重点。看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)
{
// 1. work就是外部的block,这里ctxt是对block的一个copy操作
void *ctxt = _dispatch_Block_copy(work);

dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
if (unlikely(_dispatch_block_has_private_data(work))) {
// dc_flags赋值
dc->dc_flags = dc_flags;
// block赋值到dc_ctxt中
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);
}
// 所以会走这里,func可以理解为work的方法名。
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源码,其内部主要是为了保存block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)
{
// 默认优先级0
pthread_priority_t pp = 0;
// 设置dc_flags
dc->dc_flags = dc_flags | DC_FLAG_ALLOCATED;
// 设置方法
dc->dc_func = f;
// 方法实现。
dc->dc_ctxt = ctxt;
// 设置优先级
if (!(flags & DISPATCH_BLOCK_HAS_PRIORITY)) {
pp = _dispatch_priority_propagate();
}
_dispatch_continuation_voucher_set(dc, flags);
// 对block调用的优先级处理
return _dispatch_continuation_priority_set(dc, dqu, pp, flags);
}

以上内容呢,是qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);的内部实现,接下来我们再看下一句代码
_dispatch_continuation_async

1
2
3
4
5
6
7
8
9
10
11
12
13
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。我们全局搜了一下,它是一个宏。

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

接下来,就有点迷茫了,dq_push是个什么鬼东西?全局搜一下。

我们发现,dq_push的内容是根据当前类型赋值的,比如是串行,那就是一个_dispatch_lane_push,我们这里使用的并发队列,所以,应该执行的是_dispatch_lane_concurrent_push

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void
_dispatch_lane_concurrent_push(dispatch_lane_t dq, dispatch_object_t dou,
dispatch_qos_t qos)
{
// 一堆条件判断
if (dq->dq_items_tail == NULL &&
!_dispatch_object_is_waiter(dou) &&
!_dispatch_object_is_barrier(dou) &&
_dispatch_queue_try_acquire_async(dq)) {
// 我们先看看这个东西
return _dispatch_continuation_redirect_push(dq, dou, qos);
}

// 最后会执行到这里。
_dispatch_lane_push(dq, dou, qos);
}

经过一系列判断,执行到_dispatch_continuation_redirect_push.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void
_dispatch_continuation_redirect_push(dispatch_lane_t dl,
dispatch_object_t dou, dispatch_qos_t qos)
{
if (likely(!_dispatch_object_is_redirection(dou))) {
// 这里会生成_dc,内部就不细说了,主要是为了绑定block,target
dou._dc = _dispatch_async_redirect_wrap(dl, dou);
} else if (!dou._dc->dc_ctxt) {
// 如果没有实现,赋值一个
dou._dc->dc_ctxt = (void *)
(uintptr_t)_dispatch_queue_autorelease_frequency(dl);
}
// 这里指向target
dispatch_queue_t dq = dl->do_targetq;
if (!qos) qos = _dispatch_priority_qos(dq->dq_priority);
// 又来了一个dx_push
dx_push(dq, dou, qos);
}

看到这里就有疑惑了,上一步刚执行了`dx_push·,怎么这里有来了一个?

其实就好比Person继承自NSObject,比如实现init方法,会通过isa指向父类,调用父类的方法,这里也是一样的,通过do_targetq指向父类,执行父类的方法。父类就是_dispatch_root_queue_push

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void
_dispatch_root_queue_push(dispatch_queue_global_t rq, dispatch_object_t dou,
dispatch_qos_t qos)
{
#if DISPATCH_USE_KEVENT_WORKQUEUE
dispatch_deferred_items_t ddi = _dispatch_deferred_items_get();
// 这里不是重点内容,不需要看
if (unlikely(ddi && ddi->ddi_can_stash)) {...}
#endif
#if HAVE_PTHREAD_WORKQUEUE_QOS
if (_dispatch_root_queue_push_needs_override(rq, qos)) {
return _dispatch_root_queue_push_override(rq, dou, qos);
}
#else
(void)qos;
#endif
_dispatch_root_queue_push_inline(rq, dou, dou, 1);
}

重点也就是在_dispatch_root_queue_push_inline

1
2
3
4
5
6
7
8
9
static inline void
_dispatch_root_queue_push_inline(dispatch_queue_global_t dq,
dispatch_object_t _head, dispatch_object_t _tail, int n)
{
struct dispatch_object_s *hd = _head._do, *tl = _tail._do;
if (unlikely(os_mpsc_push_list(os_mpsc(dq, dq_items), hd, tl, do_next))) {
return _dispatch_root_queue_poke(dq, n, 0);
}
}

在这个函数内部执行_dispatch_root_queue_poke,这个函数内部其实也就是一个_dispatch_root_queue_poke_slow方法。是整个dispatch中相当重要的一环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static void
_dispatch_root_queue_poke_slow(dispatch_queue_global_t dq, int n, int floor) {
...
// 这里执行跟类queue的初始化,内部是一个dispatch_once,只会初始化一次。单利,下一章介绍
_dispatch_root_queues_init();
...
// 如果是Global类型的函数,直接返回了。
if (dx_type(dq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE)
#endif
{
_dispatch_root_queue_debug("requesting new worker thread for global "
"queue: %p", dq);
r = _pthread_workqueue_addthreads(remaining,
_dispatch_priority_to_pp_prefer_fallback(dq->dq_priority));
(void)dispatch_assume_zero(r);
return;
}
...
// 这中间省略的代码是判断remaining数,也就是需要创建的线程数。
do {
_dispatch_retain(dq); // released in _dispatch_worker_thread
// 循环创建线程
while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) {
if (r != EAGAIN) {
(void)dispatch_assume_zero(r);
}
_dispatch_temporary_resource_shortage();
}
} while (--remaining);
...
} while (!os_atomic_cmpxchgv2o(dq, dgq_thread_pool_size, t_count,
t_count - remaining, &t_count, acquire));

这个是GCD内部相当重点的一个点,首先进行root_queues的初始化,然后创建线程来执行任务。

1
2
3
4
5
6
7
_dispatch_root_queues_init();

_dispatch_root_queues_init(void)
{
dispatch_once_f(&_dispatch_root_queues_pred, NULL,
_dispatch_root_queues_init_once);
}

初始化函数内部调用的dispatch_once_f,只会执行一次,这一内容,下一章会有介绍。_dispatch_root_queues_init_once重点要看的是这个内部是个啥。

_dispatch_root_queues_init_once的内部实现代码就不放出来了,太长了,主要的作用就是创建与线程直接的依赖,同时关联线程的回调方法_dispatch_worker_thread2

root_queues初始化完成之后,再创建线程,但是内部是怎么调用block实现的,下一章有介绍。

接下来,我们返回到_dispatch_lane_concurrent_push这里,也就是连续的dq_push之后,最终会执行_dispatch_lane_push

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void
_dispatch_lane_push(dispatch_lane_t dq, dispatch_object_t dou,
dispatch_qos_t qos)
{
dispatch_wakeup_flags_t flags = 0;
struct dispatch_object_s *prev;

if (unlikely(_dispatch_object_is_waiter(dou))) {
return _dispatch_lane_push_waiter(dq, dou._dsc, qos);
}

dispatch_assert(!_dispatch_object_is_global(dq));
qos = _dispatch_queue_push_qos(dq, qos);

...

os_mpsc_push_update_prev(os_mpsc(dq, dq_items), prev, dou._do, do_next);
if (flags) {
// 这里的重点是wakeup
return dx_wakeup(dq, qos, flags);
}
}

而dx_wakeup与dx_push如出一辙,都是宏定义,根据当前队列进行赋值,这里就不细说了,直接选择root类型的方法。

由于代码巨大,这里直接放了截图。

最后执行的是wakeup,要保持线程是清醒的,其实就是为了保活。直到block执行完毕。没有target没有上一层之后,执行release操作。

这个也就是dispatch_async的实现。下一章会继续block是如何调用的。

总结

  1. GCD的介绍

  2. 同步、异步函数的介绍

  3. 串行队列、并发队列

  4. 函数与队列的4种组合,以及面试题

    1. 并发不会等待一个任务执行完成才执行。
    2. 串行会等待一个任务执行完毕才执行。
    3. 异步函数下,不管是串行队列还是并行队列,都不影响block块之外的任务执。因为block内部是在新开启的线程中执行的。
    4. 同步函数下,并行队列不受影响,因为并行不需要等待上一个任务执行完成。如果是串行队列,那在当前线程下会发生死锁。
  5. 主队列dispatch_get_main_queue,全局队列dispatch_get_global_queue内部实现

  6. dispatch_queue_create创建一个队列的原理

  7. dispatch_async内部实现,异步会创建线程,然后进行weakup保活操作,block执行完成之后进行释放。

引用

libdispatch源文件
这里是用的是libdispatch-1271.40.12.tar.gz文件。