这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战c++
什么是GCD?全称是GrandCentralDispatch
纯C语⾔,提供了⾮常多强⼤的函数。总结为一句话:将任务添加到队列,而且指定执行任务的函数。 程序员
GCD是苹果公司为多核的并⾏运算提出的解决⽅案 GCD会⾃动利⽤更多的CPU内核(⽐如双核、四核) GCD会⾃动管理线程的⽣命周期(建立线程、调度任务、销毁线程) 程序员只须要告诉GCD想要执⾏什么任务,不须要编写任何线程管理代码 面试
异步dispatch_async
markdown
2. 会开启线程执⾏block的任务 3. 异步是多线程的代名词 同步dispatch_sync
多线程
队列遵循FIFO原则:先进先出并发
因为是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");
}
复制代码
咱们知道不管同步仍是异步函数都是一个耗时任务。async
再来一个,听说这个是新浪的面试题函数
- (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");
}
复制代码
此时打印崩溃:
此时在这里,2执行完以后,执行到了一个代码块(dispatch_sync
而sync的特色是阻塞,必须等到本身执行完以后才能够),而队列因为是先进先出的原则,因此此时形成了4等待块执行完成,块的执行完成须要3执行,而3又等待4执行,这样就形成了一个死锁的问题。
改进:那么咱们把4的任务删除,还会形成死锁嘛?答案是:还会死锁 观察调用栈发现死锁的函数是:_dispatch_sync_f_slow
实际上发生死锁的dispatch_async
和dispatch_sync
这两个代码块
// 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
是一个并发队列。 打印输出能够获得这些信息:
dispatch_get_main_queue
咱们要想研究的话,能够从底层的源码入手。在项目中使用GCD的地方打个断点,查看调用栈,看看底层使用的是哪一个库 由上面咱们能够看出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
,继续搜索 还有一个更简单的方式定位到这里,由上面咱们知道,主队类的
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
类型,搜索的时候小技巧把它带上,能够快速定位
_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
来接收?预知后续,咱们下一篇来说解。