前几天 Apple 开源了 Swift,顺带也开源了 swift-corelibs-libdispatch,其实也就是个跨平台的 libdispatch。之前对 GCD 的理解很多都是死记硬背下来的(而且基本上都是去面试前准备的……)。正好趁这个机会研究一下,当然,最主要的原因是看不懂 Swift 代码😂
认真看过代码之后才发现,以前对很多概念的理解都是错误的,比如 Queue,线程池等。
Queue
在之前的理解中,我以为每次创建的 queue 都是一个独立的 queue 实体,管理着它自己的线程池。
然而并不是这样。
先来看一下创建 queue 的函数 dispatch_queue_create
代码:
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
return dispatch_queue_create_with_target(label, attr,
DISPATCH_TARGET_QUEUE_DEFAULT);
// #define DISPATCH_TARGET_QUEUE_DEFAULT NULL
}
dispatch_queue_create_with_target
函数定义如下(省略部分代码):
dispatch_queue_t
dispatch_queue_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq)
{
// ...
if (!tq) {
// ...
tq = _dispatch_get_root_queue(qos, overcommit ==
_dispatch_queue_attr_overcommit_enabled);
if (slowpath(!tq)) {
DISPATCH_CLIENT_CRASH("Invalid queue attribute");
}
}
// ...
_dispatch_queue_set_override_priority(dq);
dq->do_targetq = tq;
_dispatch_object_debug(dq, "%s", __func__);
return _dispatch_introspection_queue_create(dq);
}
可以发现,其实创建自定义 queue 的时候,只不过是从 root queue 列表里找一个相应优先级的 root queue,把它设置成新 queue 的 target queue 而已。在自定义 queue 中执行 block 时实际是在它的 target queue 中执行的。
自定义 queue 更像是一个 root queue 的 “代理”。
代码里的 “root queue”,其实就是 GCD 概念里的 Global Queue。在初始化的时候,会创建 15 个 global queue,分别是:
序号 | 名称 |
---|---|
1 | com.apple.main-thread |
2 | com.apple.libdispatch-manager |
3 | com.apple.root.libdispatch-manager |
4 | com.apple.root.maintenance-qos |
5 | com.apple.root.maintenance-qos.overcommit |
6 | com.apple.root.background-qos |
7 | com.apple.root.background-qos.overcommit |
8 | com.apple.root.utility-qos |
9 | com.apple.root.utility-qos.overcommit |
10 | com.apple.root.default-qos |
11 | com.apple.root.default-qos.overcommit |
12 | com.apple.root.user-initiated-qos |
13 | com.apple.root.user-initiated-qos.overcommit |
14 | com.apple.root.user-interactive-qos |
15 | com.apple.root.user-interactive-qos.overcommit |
其中:
- #0 没有使用。
- #1 与主线程关联,定义在 init.c 文件。
- #2-3 是内部管理 queue 用的,定义在 queue.c 文件。
- #4-15 queue 定义在 queue.c 文件的
_dispatch_root_queues
数组里。
带 overcommit 参数的表示该 queue 在执行 block 时,无论系统多忙都会新开一个线程。在调用 `dispatch_get_global_queue(long identifier, unsigned long flags)
方法时,指定 flags 为 DISPATCH_QUEUE_OVERCOMMIT
即可获取此类 Queue。
关于优先级,在早期版本中比较简单,有以下这些:
- DISPATCH_QUEUE_PRIORITY_HIGH
- DISPATCH_QUEUE_PRIORITY_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW
- DISPATCH_QUEUE_PRIORITY_BACKGROUND
在支持了 Quality of Service(QoS)之后复杂了一些:
- QOS_CLASS_USER_INTERACTIVE
- QOS_CLASS_USER_INITIATED (DISPATCH_QUEUE_PRIORITY_HIGH)
- QOS_CLASS_DEFAULT (DISPATCH_QUEUE_PRIORITY_DEFAULT)
- QOS_CLASS_UTILITY (DISPATCH_QUEUE_PRIORITY_LOW)
- QOS_CLASS_BACKGROUND (DISPATCH_QUEUE_PRIORITY_BACKGROUND)
为了支持 QoS,root queue 也从 11 个增加到了 15 个。
objc.io 的这幅图很形象地描述了各种 queue 的关系:
Queue 与线程
之前我以为每个 queue 都管理着它自己的线程池,concurrent queue 的线程池里有多个线程,而 serial queue 的只有一个。
然而并不是。
所有 queue 的线程池都是统一管理的,在 Mac OS 中,是靠 pthread workqueue 实现的。(pthread workqueue 没找到详细信息,可以参考 FreeBSD 手册。PS:可以用 sysctl kern.wq_max_threads
查看 workqueue 中支持的最大线程数)。这需要 Libc 标准库与 Kernel 的支持。
所以即使对于 serial queue 来说,它所面对的也是所有的线程池。所以任务是否是并发执行的决定权在 queue 本身。
来看一下 queue 结构体的定义(不同平台可能有差异,大致如下):
struct dispatch_queue_s {
DISPATCH_STRUCT_HEADER(queue);
/* DISPATCH_QUEUE_HEADER */
uint32_t volatile dq_running;
struct dispatch_object_s *volatile dq_items_head;
/* LP64 global queue cacheline boundary */
struct dispatch_object_s *volatile dq_items_tail;
dispatch_queue_t dq_specific_q;
uint16_t dq_width;
uint16_t dq_is_thread_bound:1;
uint32_t volatile dq_override;
pthread_priority_t dq_priority;
mach_port_t dq_thread;
mach_port_t volatile dq_tqthread;
voucher_t dq_override_voucher;
unsigned long dq_serialnum;
const char *dq_label;
DISPATCH_QUEUE_CACHELINE_PADDING; // for static queues only
};
其中 dq_width 属性的值就是能够并发执行的最大任务数,concurrent queue 的值为 DISPATCH_QUEUE_WIDTH_MAX
(#define DISPATCH_QUEUE_WIDTH_MAX UINT16_MAX
),serial queue 的值为 1。
Queue 会根据自身 dq_width 值的大小来安排任务的执行。
--待续--