一:GCD初探
什么是GCD?
全称是 Grand Central Dispatch 纯 C 语⾔,提供了⾮常多强⼤的函数
GCD的优势
-
GCD 是苹果公司为多核的并⾏运算提出的解决⽅案
-
GCD 会⾃动利⽤更多的CPU内核(⽐如双核、四核)
-
GCD 会⾃动管理线程的⽣命周期(创建线程、调度任务、销毁线程)
-
程序员只需要告诉 GCD 想要执⾏什么任务,不需要编写任何线程管理代码。
对于GCD来说,很重要的两个概念就是队列和任务,执行方式分为同步和异步,队列和同步以及异步结合将产生多种组合,这些本文暂时不做介绍,推荐一篇文章给大家,非常详细的介绍了GCD的使用。
二:源码探究
libdispatch.dylib 的源码在这里可以下载,相比runtime源码,你会发现libdispatch源码注释真的少。
2.1 串行和并发队列分析
🌹 串行
dispatch_queue_t serial = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
🌹 并发
dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_create定义
其中会调用_dispatch_lane_create_with_target,这其中代码很长,最终返回了_dispatch_trace_queue_create(dq)._dq,重点就是这个dispatch_queue_t类型的dq,我们可以反向来推理。
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,dispatch_queue_t tq, bool legacy) {
代码很多 此处省略
// 返回值
return _dispatch_trace_queue_create(dq)._dq;
}
其中dq相关重点代码
根据重点dq我们从上述代码中找到关键代码
_dispatch_object_alloc:申请内存空间_dispatch_queue_init:构造函数初始化,会判断是否为并发队列,如果是,传入DISPATCH_QUEUE_WIDTH_MAX,否则传入1。也就是说,串行队列这里传入1,如果是并发队列,则传入DISPATCH_QUEUE_WIDTH_MAX。
- 对
dq进行设置,如dq_label、dq_priority等
_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){
uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);
dispatch_queue_t dq = dqu._dq;
dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |
DISPATCH_QUEUE_INACTIVE)) == 0);
if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {
dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume
if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {
dq->do_ref_cnt++; // released when DSF_DELETED is set
}
}
dq_state |= initial_state_bits;
dq->do_next = DISPATCH_OBJECT_LISTLESS;
dqf |= DQF_WIDTH(width); // 🌹🌹
os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
dq->dq_state = dq_state;
dq->dq_serialnum =
os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
return dqu;
}
- 把前面的
width传进来,赋值dqf |= DQF_WIDTH(width) DQF_WIDTH(width),也就是用来确定队列的类型,以此来区分串行队列和并发队列
我们还是回到_dispatch_lane_create_with_target方法,来继续探索dqai和vtable
方法开头的位置:
// dqai 创建 - dqa传入的属性串行还是并行
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
_dispatch_queue_attr_to_info
- 在这里进行初始化了
dqai,并判断dqa的类型,如果是并发队列,则设置并发队列为true,否则默认为串行队列。在调用_dispatch_queue_init对dq进行构造时,对队列类型进行了区分,也就是DQF_WIDTH(width)的传参,串行队列width=1,否则为并发队列。
关于vtable,找到关键代码
vtable可以理解为是一个类,或者说构造队列的模板类,qai来区分队列的类型,根据队列的类型来初始化不同的vtable。DISPATCH_VTABLE是一个宏定义的方法,全局搜索DISPATCH_VTABLE的定义
// DISPATCH_VTABLE定义
#define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)
// vtable symbols - 模板
#define DISPATCH_OBJC_CLASS(name) (&DISPATCH_CLASS_SYMBOL(name))
// 拼接形成类
#define DISPATCH_CLASS_SYMBOL(name) OS_dispatch_##name##_class
DISPATCH_VTABLE函数的传参根据不同的队列类型传参不一致。
并发队列:
传queue_concurrent参数,最终拼接后,队列类型对应的类为:OS_dispatch_queue_concurrent
串行队列:
传queue_serial参数,最终拼接后,队列类型对应的类为:OS_dispatch_queue_serial
所以vtable对应的就是队列的类型。通过拼接完成类的定义,这和我们在应用层使用的队列类型是一致的,如下:
// OS_dispatch_queue_serial
dispatch_queue_t serial = dispatch_queue_create("cc", DISPATCH_QUEUE_SERIAL);
// OS_dispatch_queue_concurrent
dispatch_queue_t conque = dispatch_queue_create("cc", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// OS_dispatch_queue_global
dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
<OS_dispatch_queue_serial: cc>
<OS_dispatch_queue_concurrent: cc>
<OS_dispatch_queue_main: com.apple.main-thread> 🌹主队列
<OS_dispatch_queue_global: com.apple.root.default-qos>
2.2 主队列分析
我们分别打印四种队列
dispatch_queue_t serial = dispatch_queue_create("中国", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t conque = dispatch_queue_create("中国", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
NSLog(@"%@-%@-%@-%@",serial,conque,mainQueue,globQueue);
打印结果:
<OS_dispatch_queue_serial: 中国>
<OS_dispatch_queue_concurrent: 中国>
<OS_dispatch_queue_main: com.apple.main-thread> 🌹主队列
<OS_dispatch_queue_global: com.apple.root.default-qos>
我们发现dispatch_get_main_queue()是没有传入参数的,从打印结果来看,串行和并发都打印出了label参数,即我们传入的中国,我们在libdispatch中搜索com.apple.main-thread,结果.....
其实并非没有参数,而是内部进行了定义,整体结果是个_dispatch_main_q的结构体,其中dq_serialnum = 1也印证了主队列其实也是串行队列的说法。
下面我们看下dispatch_get_main_queue
从官方解释中我们得知:
- 主队列是串行队列。
- 主队列在调用
main()函数之前自动创建的。
在libdispatch源码中找到dispatch_get_main_queue
其实dispatch_get_main_queue 就是通过DISPATCH_GLOBAL_OBJECT返回的,它是个宏定义。
而return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q)其中的参数_dispatch_main_q即是我们上面所说的结构体。
2.3 全局队列
* @param identifier
* A quality of service class defined in qos_class_t or a priority defined in
* dispatch_queue_priority_t.
*
* It is recommended to use quality of service class values to identify the
* well-known global concurrent queues:
🌹 优先级标识
* - QOS_CLASS_USER_INTERACTIVE
* - QOS_CLASS_USER_INITIATED
* - QOS_CLASS_DEFAULT
* - QOS_CLASS_UTILITY
* - QOS_CLASS_BACKGROUND
*
* The global concurrent queues may still be identified by their priority,
* which map to the following QOS classes:
🌹 服务质量
* - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
*
* @param flags
* Reserved for future use. Passing any value other than zero may result in
* a NULL return value.
*
* @result
* Returns the requested global queue or NULL if the requested global queue
* does not exist.
*/
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_CONST DISPATCH_WARN_RESULT DISPATCH_NOTHROW
dispatch_queue_global_t
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);
- 创建全局并发队列时可以传参数
- 根据不同服务质量或者优先等级提供不同的并发队列
根据2.2中打印的com.apple.root.default-qos,在libdispatch中搜索
- 系统会维护一个
全局队列集合, - 根据不同的
服务质量或者优先等级提供不同的全局队列。 - 我们在开发工作中默认使用:
dispatch_get_global_queue(0, 0)。
参考:
iOS多线程:『GCD』详尽总结