OC 底层原理(16)— 多线程二(GCD初探、创建原理、根队列分析)

665

GCD 初探

什么是GCD?

GCD 全称是 Grand Central Dispatch, 纯 C 语言,提供了非常多强大的函数

GCD 的优势

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

函数

使用例子

- (void)syncTest{
    // 任务 队列 函数
    dispatch_block_t block = ^{
        NSLog(@"hello word");
    };
    dispatch_queue_t queue = dispatch_queue_create("com.janice.cn", NULL);
    dispatch_async(queue, block);
}

以上便是将任务添加到队列,并且指定执行任务的函数

详解:

  1. 任务使用 block 封装,任务的 block 没有参数也没有返回值;
  2. 执行任务的函数
  • 异步 'dispatch_async'
    • 不用等待当前语句执行完毕,就可以执行下一条语句
    • 会开启线程执行 block 的任务
    • 异步是多线程的代名词
  • 同步 'dispatch_sync'
    • 必须等待当前语句执行完毕,才会执行下一条语句
    • 不会开启线程
    • 在当前执行 block 的任务

队列 (FIFO)

串行队列

一次只能执行一个,上一个任务没有执行完,就无法继续执行下一个任务,也就是效率比较低,任务耗时较长,DISPATCH_QUEUE_SERIAL。

并行队列

并发队列会开启多个线程来执行任务,所以可以同时执行多个任务,任务执行的顺序也不会固定,DISPATCH_QUEUE_CONCURRENT。

队列和函数

  1. 同步函数串行队列
  • 不会开启线程,在当前线程执行任务
  • 任务串行执行,任务一个接着一个
  • 会产生堵塞
  1. 同步函数并发队列
  • 不会开启线程,在当前线程执行任务
  • 任务一个接着一个
  1. 异步函数串行队列
  • 开启线程一条新线程
  • 任务一个接着一个
  1. 异步函数并发队列
  • 开启线程,在当前线程执行任务
  • 任务异步执行,没有顺序,CPU 调度有关

执行顺序练习

练习一

- (void)textDemo{
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

执行顺序:1,5,2,4,3

练习二

- (void)wbinterDemo{
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.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");
    });
}

A: 1230789 B: 1237890 C: 3120798 D: 2137890

正确答案:A、C

练习三

- (void)textDemo2{
    // 串行队列
    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");
}

中间执行任务队列示意图:

说明:

  • 按照代码顺序,打印 2 任务拍照在队列的第一位
  • 然后就是同步函数任务块
  • 然后就是打印 4 任务
  • 然后调步到同步函数任务块是,发现里面还有个打印 3 任务,按照队列的 FIFO 特性,于是就在打印 4 任务的后面。

执行流程

  1. 打印 2 ;
  2. 执行同步函数。
  3. 因为打印 4 任务之前,是个同步函数,所以就会堵塞,也就是说前面的同步函数不执行完,打印 4 任务就不会被执行
  4. 同步函数里面的打印 3 任务,又要等前面的打印 4 任务执行了才能被执行
  5. 因此这个流程机会形成一个死锁,互相等待。打印 4 任务等待前面的同步函数块执行完;同步函数里面的打印 3 任务又在等待打印 4 任务执行完。所以永远都是等待,无法往下执行。

练习四

- (void)textDemo3{
    // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    // 异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        NSLog(@"4");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
    });
    NSLog(@"5");
}

打印结果:

练习四在练习三的基础上做了一丢丢改变,将打印 4 任务放在同步函数块的前面,再来分析一下

中间执行任务队列示意图:

说明:

  • 按照代码顺序,打印 2 任务拍照在队列的第一位
  • 然后就是打印 4 任务
  • 然后就是同步函数任务块
  • 然后调步到同步函数任务块是,发现里面还有个打印 3 任务,按照队列的 FIFO 特性,于是就在同步函数任务块的后面。

执行流程

  1. 打印 2 ;
  2. 打印 4 ;
  3. 执行同步函数块任务,函数块执行完就意味着要等打印 3 任务执行完,才算结束。
  4. 因为打印 3 任务之前,是个同步函数块,所以就会堵塞,也就是说前面的同步函数块不执行完,打印 3 任务就不会被执行;
  5. 因此这个流程机会形成一个死锁,互相等待。打印 3 任务等待前面的同步函数块执行完;同步函数里面的打印 3 任务又在等待同步函数块执行完。所以永远都是等待,无法往下执行。

打印结果:

**总结:**练习三、练习四 ,如果异步函数里面嵌同步函数,就会发生死锁!!!。

死锁

  • 主线程因为同步函数的原因等着先执行任务
  • 主队列等着主线程的任务执行完毕再执行自己的任务
  • 主队列和主线程相互等待会造成死锁

队列创建原理

dispatch_queue_create 是如何创建的?

就需要去探索队列创建的源码

获取地址:链接:pan.baidu.com/s/1Avpzm947… 提取码:l8ci

创建队列过程源码分析

  • 首先找到 dispatch_queue_create 入口

  • 之前我们在实际创建的队列的时候,想要串行队列或并发队列是对接口的第二个参数进行设置,DISPATCH_QUEUE_SERIAL(串行)、DISPATCH_QUEUE_CONCURRENT(并行),如下

  • DISPATCH_QUEUE_SERIAL 的定义

看到 DISPATCH_QUEUE_SERIAL 赋值为 NULL

  • DISPATCH_QUEUE_CONCURRENT 的定义

所以在探索源码的时候我们也看下源码是怎么区分的

_dispatch_lane_create_with_target 源码

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_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, // 4
				overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq; // 0 1
		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) {
		// 通过dqai.dqai_concurrent 来区分并发和串行
		// OS_dispatch_queue_concurrent_class
		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;
		}
	}
    
	// 开辟内存 - 生成响应的对象 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;
}
  • 分析 _dispatch_lane_create_with_target 源码

外面传进来的队列类型参数为dpa,于是看下这段代码

看到有个判断,如果 dpa 为空,就直接返回 dqai,前面有贴出,当 串行队列 DISPATCH_QUEUE_SERIAL 的时候的定义就是为空 ,否则做其他处理,返回

  • 串行和并行创建的地方

创建关键步骤:

  1. 第一步初始化并分配内存空间

  2. 构造方法

    1)第一个参数 dq 对象

    2)dqai.dqai_concurrent 第三参数,如果为并发队列便标识,传 DISPATCH_QUEUE_WIDTH_MAX,否则 1.

DISPATCH_QUEUE_WIDTH_MAX 根据宏定义计算出来 0x1000 - 2 = 0xFFE

  1. 设置标签
  2. 设置优先级
  3. overcommit:串行为1 ,并发为 2。

overcommit 的赋值

  1. 设置target
dq->do_targetq = tq;
  1. tq 的创建

根队列分析

打印获取到的主队列和全局并发队列,因为是全局静态结构体

主队列结构体

全局队列属性打印

通过打印可知,全局变量的 width = 0xfff,上面有打印并发队列的 width = 0xfffe

队列结构体

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

出来 main queue 其他指针都是由 root queue 模板创建的

dispatch_object_t

typedef struct dispatch_object_s {
private:
	dispatch_object_s();
	~dispatch_object_s();
	dispatch_object_s(const dispatch_object_s &);
	void operator=(const dispatch_object_s &);
} *dispatch_object_t;