底层探索-多线程之GCD(队列)

848 阅读4分钟

一:GCD初探

什么是GCD?

全称是 Grand Central Dispatch 纯 C 语⾔,提供了⾮常多强⼤的函数

GCD的优势

  • GCD 是苹果公司为多核的并⾏运算提出的解决⽅案

  • GCD 会⾃动利⽤更多的CPU内核(⽐如双核、四核)

  • GCD 会⾃动管理线程的⽣命周期(创建线程、调度任务、销毁线程)

  • 程序员只需要告诉 GCD 想要执⾏什么任务,不需要编写任何线程管理代码。

对于GCD来说,很重要的两个概念就是队列任务,执行方式分为同步异步,队列同步以及异步结合将产生多种组合,这些本文暂时不做介绍,推荐一篇文章给大家,非常详细的介绍了GCD的使用。

iOS多线程:『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定义

image.png

其中会调用_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相关重点代码

image.png

根据重点dq我们从上述代码中找到关键代码

  • _dispatch_object_alloc:申请内存空间
  • _dispatch_queue_init:构造函数初始化,会判断是否为并发队列,如果是,传入DISPATCH_QUEUE_WIDTH_MAX,否则传入1。也就是说,串行队列这里传入1,如果是并发队列,则传入DISPATCH_QUEUE_WIDTH_MAX

image.png

  • dq进行设置,如dq_labeldq_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),也就是用来确定队列的类型,以此来区分串行队列并发队列

image.png

我们还是回到_dispatch_lane_create_with_target方法,来继续探索dqaivtable

iShot2021-08-30 15.04.01.png

方法开头的位置:

// dqai 创建 - dqa传入的属性串行还是并行
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

_dispatch_queue_attr_to_info

image.png

  • 在这里进行初始化了dqai,并判断dqa的类型,如果是并发队列,则设置并发队列true,否则默认为串行队列。在调用_dispatch_queue_initdq进行构造时,对队列类型进行了区分,也就是DQF_WIDTH(width)的传参,串行队列width=1,否则为并发队列

关于vtable,找到关键代码

image.png

vtable可以理解为是一个类,或者说构造队列的模板类,qai来区分队列的类型,根据队列的类型来初始化不同的vtableDISPATCH_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,结果.....

image.png

其实并非没有参数,而是内部进行了定义,整体结果是个_dispatch_main_q的结构体,其中dq_serialnum = 1也印证了主队列其实也是串行队列的说法。

下面我们看下dispatch_get_main_queue

image.png

从官方解释中我们得知:

  • 主队列是串行队列。
  • 主队列在调用main()函数之前自动创建的。

libdispatch源码中找到dispatch_get_main_queue

image.png

其实dispatch_get_main_queue 就是通过DISPATCH_GLOBAL_OBJECT返回的,它是个宏定义。

image.png

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中搜索

image.png

  • 系统会维护一个全局队列集合
  • 根据不同的服务质量或者优先等级提供不同的全局队列
  • 我们在开发工作中默认使用:dispatch_get_global_queue(0, 0)

参考:
iOS多线程:『GCD』详尽总结