GCD原理分析上

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

GCD简介

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

GCD的优势

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

异步与同步

异步dispatch_async

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

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

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

串行队列和并发队列

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

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

由于是FIFO,所以串行队列按顺序执行。并发队列只是调度任务并不是执行任务。

队列函数的应用

以下的函数执行顺序是怎样的

- (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

我们知道无论同步还是异步函数都是一个耗时任务。

串行和并发.png

再来一个,据说这个是新浪的面试题

- (void)wbinterDemo{
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_SERIAL);

    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来接收?预知后续,我们下一篇来讲解。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享