GCD原理分析上篇

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战c++

GCD简介

什么是GCD?全称是GrandCentralDispatch 纯C语⾔,提供了⾮常多强⼤的函数。总结为一句话:将任务添加到队列,而且指定执行任务的函数。 ​程序员

GCD的优点

GCD是苹果公司为多核的并⾏运算提出的解决⽅案 GCD会⾃动利⽤更多的CPU内核(⽐如双核、四核) GCD会⾃动管理线程的⽣命周期(建立线程、调度任务、销毁线程) 程序员只须要告诉GCD想要执⾏什么任务,不须要编写任何线程管理代码 ​面试

异步与同步

异步dispatch_asyncmarkdown

  1. 不⽤等待当前语句执⾏完毕,就能够执⾏下⼀条语句

  2. 会开启线程执⾏block的任务   3. 异步是多线程的代名词 同步dispatch_sync多线程

  1. 必须等待当前语句执⾏完毕,才会执⾏下⼀条语句

串行队列和并发队列

队列遵循FIFO原则:先进先出并发

串行队列.png 并发队列.png

因为是FIFO,因此串行队列按顺序执行。并发队列只是调度任务并非执行任务。 ​app

队列函数的应用

如下的函数执行顺序是怎样的异步

- (void)textDemo2{
    // 同步队列
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
复制代码

image.png

咱们知道不管同步仍是异步函数都是一个耗时任务。async

串行和并发.png

再来一个,听说这个是新浪的面试题函数

- (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
}
复制代码

正确答案是AC

分析:首先开启的是一个串行队列,12行的代码阻塞的是13行如下的,因此3在0以前,123没有顺序,789也没有顺序,使用排除法获得AC

死锁问题

若是把队列修改成串行队列那么此时调用的顺序为:

- (void)textDemo1{
    dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
复制代码

此时打印崩溃:

image.png

死锁问题.png

此时在这里,2执行完以后,执行到了一个代码块(dispatch_sync而sync的特色是阻塞,必须等到本身执行完以后才能够),而队列因为是先进先出的原则,因此此时形成了4等待块执行完成,块的执行完成须要3执行,而3又等待4执行,这样就形成了一个死锁的问题。 ​

改进:那么咱们把4的任务删除,还会形成死锁嘛?答案是:还会死锁 观察调用栈发现死锁的函数是:_dispatch_sync_f_slow 实际上发生死锁的dispatch_asyncdispatch_sync这两个代码块 ​

GCD建立队列四种方式

// OS_dispatch_queue_serial 串行
    dispatch_queue_t serial = dispatch_queue_create("hb", DISPATCH_QUEUE_SERIAL);
    // OS_dispatch_queue_concurrent 并发
    dispatch_queue_t conque = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    // DISPATCH_QUEUE_SERIAL max && 1
    // queue 对象 alloc init class
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
复制代码

咱们知道dispatch_get_main_queue是一个串行队列而且这个队列是在main()调用以前主线程自动建立的,dispatch_get_global_queue是一个并发队列。 打印输出能够获得这些信息: image.png

主队列dispatch_get_main_queue

咱们要想研究的话,能够从底层的源码入手。在项目中使用GCD的地方打个断点,查看调用栈,看看底层使用的是哪一个库 image.png 由上面咱们能够看出GCD在libdispatch.dylib,接下来咱们在openSource中去下载源码。 在源码中搜索这个dispatch_get_main_queue能够定位到这里的代码

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) ((type)&(object))
//dispatch_queue_main_t & _dispatch_main_q 
复制代码

能够得出类型是dispatch_queue_main_t,对象是_dispatch_main_q,继续搜索 image.png 还有一个更简单的方式定位到这里,由上面咱们知道,主队类的lable = **com.apple.main-thread**,因此咱们能够直接在libdispatch里面搜索也可以直接定位到这里。

回到上面的函数,咱们发现主队列的赋值:

.dq_label = "com.apple.main-thread",
	.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
	.dq_serialnum = 1,
复制代码

dq_serialnum = 1就是串行队列嘛

那么咱们在源码里面研究下串行队列的建立特性,就知道这个条件是否是必然条件了。那么队列是怎么建立的呢?咱们知道这里用到了一个函数dispatch_queue_create,那么就来研究下底层这个函数是怎么实现的吧,这样这些疑问就会立刻明了 ​

dispatch_queue_create底层源码

第一个参数是const类型,搜索的时候小技巧把它带上,能够快速定位 image.png _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)
	// 初始化queue
	// Step 2: Initialize the queue
		
	// 申请和开辟内存
	dispatch_lane_t dq = _dispatch_object_alloc(vtable,
			sizeof(struct dispatch_lane_s));
    // 构造函数初始化 dqai.dqai_concurrent:是不是并发
	_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;
}
复制代码

在函数构造初始化的时候有这么一行代码dqai.dqai_concurrent ? DISPATCH_QUEUE_WIDTH_MAX : 1 再进入_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)
{
	// ...
	dqf |= DQF_WIDTH(width);
    dq->dq_serialnum =
			os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
	// ...
	return dqu;
}
复制代码

能够得出: 若是是并发队列dqf |= DQF_WIDTH(DISPATCH_QUEUE_WIDTH_MAX) 若是是串行队列dqf |= DQF_WIDTH(1) 继续研究dq->dq_serialnum os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed)

// skip zero
// 1 - main_q 主队列
// 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_serial_numbers只是表明的是建立的队列的归属(串行仍是并发),因此上面的问题dq->dq_serialnum = 1就是建立的主队列也是串行队列 ​

扩展os_atomic_inc_orig

搜索os_atomic_inc_orig发现是个宏定义 os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed)os_atomic_add_orig((17), 1, relaxed) _os_atomic_c11_op_orig((17), (v), relaxed, add, +) atomic_fetch_add_explicit(_os_atomic_c11_atomic(17), v, memory_order_relaxedm) atomic_fetch_add_explicit是一个c++函数,也就是17 + 1 ​

_dispatch_object_alloc

在creat的底层源码中,申请和开启内存使用的是这行代码

dispatch_lane_t dq = _dispatch_object_alloc(vtable,
			sizeof(struct dispatch_lane_s));
复制代码

按照咱们的常识,建立队列嘛,确定使用 _dispatch_queue_alloc这个函数,可是这里为何使用的是_dispatch_object_alloc而且用dispatch_lane_t来接收?预知后续,咱们下一篇来说解。

相关文章
相关标签/搜索