iOS Objective-C GCD之queue(队列)篇
GCD
全称Grand Central Dispatch
,是苹果为多核的并行运算提出的解决方案,由C
语言实现,GCD
会自动利用更多的CPU
内核,自动管理线程的声明周期。GCD
的底层实现来自libdispatch
库,我们可以在Apple Open Source下载各个版本的libdispatch源码。在这篇文章中我们着重介绍GCD
中的队列。
1. 队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front
)进行删除操作,而在表的后端(rear
)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
在代码编程中,就是我们在队尾插入一块任务,然后Runloop调度线程去执行任务,执行完就出队,继续执行下一个。这些任务可以是一个简单的运算,也可以是加载一张图片,在iOS中也可以是一个block
代码块。
在使用GCD
的时候,我们都会获取一个队列,所以说GCD
的使用离不开队列,实际上获取的队列的类型是dispatch_queue_t
。
队列分为串行和并行(并发)
- 串行队列中任务只会顺序执行,类似于公交车,前门一个一个的上车,后门一个一个的下车
- 并行队列中任务可以并行执行,类似于火车或地铁,可以多个门口上车,多个门口下车
在系统中队列还分为全局队列和主队列
- 全局队列 由系统创建,在iOS中苹果给程序员提供了一个
global_queue
的全局并发队列,,在多线程开发中如果没有特殊需求,在执行异步任务的时候可以直接使用全局队列。 - 主队列 同样由系统创建,每个应用程序对应唯一一个主队列,主队列与主线程是绑定的,在iOS中主队列通常都是用来更新UI的。
1.1 iOS中创建队列的方式
1.1.1 获取主队列
- 获取主队列使用的函数
dispatch_get_main_queue()
- 主队列是在应用启动的时,
main
函数执行前系统自动创建的,这个队列被绑定在主线程上。
1.1.2 获取全局队列
- 获取全局队列的函数
dispatch_get_global_queue(<#intptr_t identifier#>, <#uintptr_t flags#>)
- 第一个参数我们一般传0,但是系统也有一些枚举值如下:
枚举 | 值 | 优先级 |
---|---|---|
DISPATCH_QUEUE_PRIORITY_HIGH | 2 | 高优先级 |
DISPATCH_QUEUE_PRIORITY_DEFAULT | 0 | 默认优先级 |
DISPATCH_QUEUE_PRIORITY_LOW | -2 | 低优先级 |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | INT16_MIN | 后台优先级 |
- 第二个参数是标签
flag
,我们在使用的时候传0的时候也很多
1.1.3 创建一个新队列
- 创建新队列使用的函数:
dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)
- 第一个参数
label
用来标识queue
的字符串一般使用倒域名的形式"com.xxx.xxx" - 第二个参数
attr
队列属性,DISPATCH_QUEUE_SERIAL
也就是NULL
会创建串行队列,DISPATCH_QUEUE_CONCURRENT
会创建并发队列。
1.2 函数
将任务添加到队列,并且指定执行任务的函数
- 任务使用
block
封装,没有参数也没有返回值 - 执行任务的异步函数:
dispatch_async
- 不用等待当前语句执行完毕就可以执行下一条语句
- 会开启线程执行
block
的任务 - 异步是多线程的代名词
- 执行任务的同步函数
dispatch_sync
- 必须等待当前语句执行完毕才会执行下一条语句
- 不会开启线程
- 在当前线程执行
block
任务
1.3 队列和函数
同步函数和串行队列:
- 不会开启线程在当前线程执行任务
- 任务串行执行,任务一个接着一个
- 会产生阻塞
同步函数并发队列:
- 不会开启线程,在当前线程执行任务
- 任务一个接着一个
异步函数串行队列:
- 开启一条心线程
- 任务一个接着一个
异步函数并发队列:
- 开启线程,并在当前线程执行任务
- 任务异步执行,没有顺序,CPU调度有关
1.4 死锁
- 在主线程使用同步函数等着执行任务
- 主队列等着主线程的任务执行完毕在执行自己的任务
- 主队列和主线程相互等待就会造成死锁
2. 队列的底层原理
我们下载一份新的libdispath
源码进行查看,此处下载的是libdispatch-1173.40.5
2.1 主队列
我们全局搜索dispatch_get_main_queue(
,加个(
是为了方便查找,剔除无关项。通过搜索结果我们可以筛选到如下代码:
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
我们可以看到主队列的底层实现是由一个DISPATCH_GLOBAL_OBJECT的宏,传入了dispatch_queue_main_t
和_dispatch_main_q
两个参数,下面我们在全局搜索一下这个宏。(如果用Visual Studio Code查看可以直接跳转)
全局搜索后可以得到下面三个结果:
// OS_OBJECT_USE_OBJC
#define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))
// defined(__cplusplus) && !defined(__DISPATCH_BUILDING_DISPATCH__)
#define DISPATCH_GLOBAL_OBJECT(type, object) (static_cast(&(object)))
// /* Plain C */
#define DISPATCH_GLOBAL_OBJECT(type, object) ((type)&(object))
分别对应注释中的环境,在此处我们用的是第三个,纯C 语言环境(如果使用VSCode也会直接跳转到这行)。
我们试着再去点击dispatch_queue_main_t
进行跳转,代码如下:
#if defined(__DISPATCH_BUILDING_DISPATCH__) && !defined(__OBJC__)
typedef struct dispatch_queue_static_s *dispatch_queue_main_t;
#else
DISPATCH_DECL_SUBCLASS(dispatch_queue_main, dispatch_queue_serial);
#endif
对于objc
环境是else里面的DISPATCH_DECL_SUBCLASS(dispatch_queue_main, dispatch_queue_serial);
,DISPATCH_DECL_SUBCLASS
这个宏定义如下
#if OS_OBJECT_SWIFT3
#define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS_SWIFT(name, dispatch_object)
#define DISPATCH_DECL_SUBCLASS(name, base) OS_OBJECT_DECL_SUBCLASS_SWIFT(name, base)
#else // OS_OBJECT_SWIFT3
#define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS(name, dispatch_object)
#define DISPATCH_DECL_SUBCLASS(name, base) OS_OBJECT_DECL_SUBCLASS(name, base)
这里选择后面这个非Swift3
的,继续查找OS_OBJECT_DECL_SUBCLASS
定义如下:
#define OS_OBJECT_DECL_SUBCLASS(name, super) \
OS_OBJECT_DECL_IMPL(name, )
这里就是一个IMP指针了,应该是系统定义好的,我们可以通过指针去取值。其实我们也可以看得出来主队列就是serial
的子类,一个特殊的串行队列。
2.2 全局队列
老样子,我们全局搜索dispatch_get_global_queue(
,最后通过筛选找到如下代码:
dispatch_queue_global_t
dispatch_get_global_queue(long priority, unsigned long flags)
{
dispatch_assert(countof(_dispatch_root_queues) ==
DISPATCH_ROOT_QUEUE_COUNT);
if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {
return DISPATCH_BAD_INPUT;
}
dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);
#if !HAVE_PTHREAD_WORKQUEUE_QOS
if (qos == QOS_CLASS_MAINTENANCE) {
qos = DISPATCH_QOS_BACKGROUND;
} else if (qos == QOS_CLASS_USER_INTERACTIVE) {
qos = DISPATCH_QOS_USER_INITIATED;
}
#endif
if (qos == DISPATCH_QOS_UNSPECIFIED) {
return DISPATCH_BAD_INPUT;
}
return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
}
可以看出该处代码就是一层封装,进行一些容错处理,然后获取到优先级(qos),最后调用了_dispatch_get_root_queue
_dispatch_get_root_queue 源码:
static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");
}
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}
我们可以在_dispatch_get_root_queue
中看出首先是对优先级的一个容错判断,然后从一个数组中取出一个队列返回。那么我们就来看看这个数组,搜索很麻烦,我用VSCode
跳转找到的。源码如下:
struct dispatch_queue_global_s _dispatch_root_queues[] = {
#define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \
((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \
DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \
DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)
#define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \
[_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \
DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \
.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \
.do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \
.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
.dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \
_dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \
_dispatch_priority_make(DISPATCH_QOS_##n, 0)), \
__VA_ARGS__ \
}
_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,
.dq_label = "com.apple.root.maintenance-qos",
.dq_serialnum = 4,
),
_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.maintenance-qos.overcommit",
.dq_serialnum = 5,
),
_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,
.dq_label = "com.apple.root.background-qos",
.dq_serialnum = 6,
),
_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.background-qos.overcommit",
.dq_serialnum = 7,
),
_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,
.dq_label = "com.apple.root.utility-qos",
.dq_serialnum = 8,
),
_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.utility-qos.overcommit",
.dq_serialnum = 9,
),
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
.dq_label = "com.apple.root.default-qos",
.dq_serialnum = 10,
),
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.default-qos.overcommit",
.dq_serialnum = 11,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
.dq_label = "com.apple.root.user-initiated-qos",
.dq_serialnum = 12,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.user-initiated-qos.overcommit",
.dq_serialnum = 13,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,
.dq_label = "com.apple.root.user-interactive-qos",
.dq_serialnum = 14,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.user-interactive-qos.overcommit",
.dq_serialnum = 15,
),
};
可以看到这个数组中定义了很多队列供我们使用,根据优先级和overcommit
的值通过相关计算得出需要取得数组中第几个队列。
下面我们通过代码验证一下:
验证代码:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"global queue: %@", [NSThread currentThread]);
});
我们可以从调用堆栈里看出,该代码执行线程所用的队列是com.apple.root.default-qos (concurrent)
,对应数组中如下:
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
.dq_label = "com.apple.root.default-qos",
.dq_serialnum = 10,
)
当我们把优先级换成DISPATCH_QUEUE_PRIORITY_BACKGROUND
结果如下:队列为com.apple.root.background-qos (concurrent)
其实我们会有个问题?我们不是在探索队列的底层原理吗?也就是在找队列是如何创建的,那么我们从数组中取出的队列是如何创建的呢?我们只能搜索_dispatch_root_queues
了,好在不是很多,经过我们一个一个的排查,最后找到了_dispatch_introspection_init
函数,在这里通过循环遍历调用_dispatch_trace_queue_create
函数一个一个的取出数组里的地址指针创建的队列。这里还有个小插曲,我一开始找到了一个_dispatch_root_queues_init
函数,其内部调用了_dispatch_root_queues_init_once
函数,内部也有个类似的循环取出_dispatch_root_queues
数组中元素去init
的操作,但是仔细一看这里并不是创建队列,而是调用_dispatch_root_queue_init_pthread_pool
为每个队列初始化线程池。
_dispatch_introspection_init源码(创建数组中的队列):
void
_dispatch_introspection_init(void)
{
_dispatch_introspection.debug_queue_inversions =
_dispatch_getenv_bool("LIBDISPATCH_DEBUG_QUEUE_INVERSIONS", false);
// Hack to determine queue TSD offset from start of pthread structure
uintptr_t thread = _dispatch_thread_self();
thread_identifier_info_data_t tiid;
mach_msg_type_number_t cnt = THREAD_IDENTIFIER_INFO_COUNT;
kern_return_t kr = thread_info(pthread_mach_thread_np((void*)thread),
THREAD_IDENTIFIER_INFO, (thread_info_t)&tiid, &cnt);
if (!dispatch_assume_zero(kr)) {
_dispatch_introspection.thread_queue_offset =
(void*)(uintptr_t)tiid.dispatch_qaddr - (void*)thread;
}
_dispatch_thread_key_create(&dispatch_introspection_key,
_dispatch_introspection_thread_remove);
_dispatch_introspection_thread_add(); // add main thread
for (size_t i = 0; i < DISPATCH_ROOT_QUEUE_COUNT; i++) {
_dispatch_trace_queue_create(&_dispatch_root_queues[i]);
}
#if DISPATCH_USE_MGR_THREAD && DISPATCH_USE_PTHREAD_ROOT_QUEUES
_dispatch_trace_queue_create(_dispatch_mgr_q.do_targetq);
#endif
_dispatch_trace_queue_create(&_dispatch_main_q);
_dispatch_trace_queue_create(&_dispatch_mgr_q);
}
_dispatch_trace_queue_create
的实现我们放在后面在进一步分析。
我们搜索_dispatch_introspection_init
可以发现他的调用是在libdispatch_init
函数中,在这个函数中还调用了如下的初始化函数
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();
_voucher_init();
_dispatch_introspection_init();
2.3 自我创建的队列
我们知道队列分为串行和并行,那么我们就分别创建下,然后打印一下他们的信息。也顺带打印了一下mainQueue
和globalQueue
名称 | 类型 | target | width |
---|---|---|---|
main | mian | com.apple.root.default-qos.overcommit | 0x1 |
global | global | \ | 0xfff |
serial | serial | com.apple.root.default-qos.overcommit | 0x1 |
concurrent | concurrent | com.apple.root.default-qos | 0xffe |
我们可以看到不同队列对应的都是OS_dispatch_queue_XXX
类型,其中主队列和serial
队列的target
是一样的,在width
方面主队列与serial
队列依旧一致,可以说主队列是一个特殊的串行队列;global
的值是0xfff
,concurrent
的值是0xffe
相对于global
少了一个,那么为什么会这样呢?我们马上开始探索。
首先我们全局搜索一下dispatch_queue_create(const
,来看看dispatch_queue_create
是如何实现的。
dispatch_queue_create源码:
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
return _dispatch_lane_create_with_target(label, attr,
DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
我们发现dispatch_queue_create
就是一个隔离的操作,队列的创建实际上是由_dispatch_lane_create_with_target
函数来实现的,大概有120多行代码。这里就不上代码了,感兴趣的可以下载源码去查看,我们挑重点来分析。
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
首先是上面这行代码,一进来就通过传入的并行和串行的参数初始化一个dispatch_queue_attr_info_t
类型的dqai
。那么dispatch_queue_attr_info_t
是个什么东西呢?我们可以通过全局搜索或者VSCode
跳转进行查看。
struct dispatch_queue_attr_s {
OS_OBJECT_STRUCT_HEADER(dispatch_queue_attr);
};
typedef struct dispatch_queue_attr_info_s {
dispatch_qos_t dqai_qos : 8;
int dqai_relpri : 8;
uint16_t dqai_overcommit:2;
uint16_t dqai_autorelease_frequency:2;
uint16_t dqai_concurrent:1;
uint16_t dqai_inactive:1;
} dispatch_queue_attr_info_t;
通过查看我们可以知道dispatch_queue_attr_info_t
是一个结构体位域,结构体位域可以通过位运算得到我们想要的内容,过滤掉我们不想要的数据。下面我们在来看看这个结构体是如何创建的吧。
_dispatch_queue_attr_to_info源码:
dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
dispatch_queue_attr_info_t dqai = { };
if (!dqa) return dqai;
#if DISPATCH_VARIANT_STATIC
if (dqa == &_dispatch_queue_attr_concurrent) {
dqai.dqai_concurrent = true;
return dqai;
}
#endif
if (dqa < _dispatch_queue_attrs ||
dqa >= &_dispatch_queue_attrs[DISPATCH_QUEUE_ATTR_COUNT]) {
DISPATCH_CLIENT_CRASH(dqa->do_vtable, "Invalid queue attribute");
}
size_t idx = (size_t)(dqa - _dispatch_queue_attrs);
dqai.dqai_inactive = (idx % DISPATCH_QUEUE_ATTR_INACTIVE_COUNT);
idx /= DISPATCH_QUEUE_ATTR_INACTIVE_COUNT;
dqai.dqai_concurrent = !(idx % DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT);
idx /= DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT;
dqai.dqai_relpri = -(int)(idx % DISPATCH_QUEUE_ATTR_PRIO_COUNT);
idx /= DISPATCH_QUEUE_ATTR_PRIO_COUNT;
dqai.dqai_qos = idx % DISPATCH_QUEUE_ATTR_QOS_COUNT;
idx /= DISPATCH_QUEUE_ATTR_QOS_COUNT;
dqai.dqai_autorelease_frequency =
idx % DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
idx /= DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
dqai.dqai_overcommit = idx % DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
idx /= DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
return dqai;
}
从_dispatch_queue_attr_to_info
源码中我们不难看出对于串行队列也就dqa
为NULL
的时候直接返回一个空的的dqai
,其中有一个idx
是苹果的一种算法得到的一个值,用于对后续的结构体进行一系列的默认配置和赋值,这里我们着重关注一下dqai.dqai_concurrent
,对于并行队列它是有值的,后面会用到。取得dqai
后,我们回到_dispatch_lane_create_with_target
函数继续分析。经过一系列的容错分析后我们来到一处具有分水岭意义的代码:
const void *vtable;
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
if (dqai.dqai_concurrent) {
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
也就是我们上面提到dqai.dqai_concurrent
如果有值就创建并行队列,没值就是串行队列。这里通过对vtable
赋不同的值来区分,然后通过vtable
开辟内存,生成dq
(dispatch queue),代码如下:
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));
然后通过_dispatch_queue_init
函数进一步初始化我们的dq。
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
我们可以看到_dispatch_queue_init
的第三参数在上面的代码中通过一个三目运算取不同的值进行传入,判断条件就是dqai.dqai_concurrent
,其实就是串行队列取1
并行队列是个宏DISPATCH_QUEUE_WIDTH_MAX
,我们跳转到这个宏(VSCode跳转的),代码如下:
#define DISPATCH_QUEUE_WIDTH_FULL_BIT 0x0020000000000000ull
#define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX (DISPATCH_QUEUE_WIDTH_FULL - 2)
#define DISPATCH_QUEUE_USES_REDIRECTION(width) \
({ uint16_t _width = (width); \
_width > 1 && _width < DISPATCH_QUEUE_WIDTH_POOL; })
可以看到这个值跟我们上面打印的width
的值不谋而合,对于全局并发队列是0x1000-1 = 0xfff,对于自定义的并发队列值是0x1000-2 = 0xffe。如果想要验证DISPATCH_QUEUE_WIDTH_POOL
也就是0x1000-1 = 0xfff是global
的width
可以直接全局搜索验证,这里就不多说了。
我们继续往下看,_dispatch_queue_init
的第四个参数是初始状态位,因为队列是有状态的就是在这里初始化的。接下来就是将label
的值和优先级``赋值给dq
,以及对队列的是否是就绪状态是对优先级的处理,还有不是补不活跃状态的队列也要进一步处理,代码如下:
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;
这个tq
是传入的,这里我们传入的时候是DISPATCH_TARGET_QUEUE_DEFAULT
也就是NULL
,那么这里的tq
是在哪里赋值的呢?我们回过头去找一找,在代码中很多判断tq
的地方,但都因为tq
为NULL
所以都没有进入相关分支,经过我们查找,在如下代码处给tq
赋了值
if (!tq) {
tq = _dispatch_get_root_queue(
qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
if (unlikely(!tq)) {
DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
}
}
_dispatch_get_root_queue源码:
static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");
}
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}
还是上面global queue
时的代码,也是从_dispatch_root_queues
数组中取出的值,所以说tq
中记录了dq_label
的值,也就是我们一开始打印时的target
的值。
知道tq
是如何取值后我们继续分析,最后调用了return _dispatch_trace_queue_create(dq)._dq;
返回创建的队列,这个函数在我们讲解global queue
的时候,对于_dispatch_root_queues
数组中的队列创建的时候也是最终调用了该函数,那么我们就接着看看这个函数都做了什么事情吧。
DISPATCH_ALWAYS_INLINE
static inline dispatch_queue_class_t
_dispatch_trace_queue_create(dispatch_queue_class_t dqu)
{
_dispatch_only_if_ktrace_enabled({
uint64_t dq_label[4] = {0}; // So that we get the right null termination
dispatch_queue_t dq = dqu._dq;
strncpy((char *)dq_label, (char *)dq->dq_label ?: "", sizeof(dq_label));
_dispatch_ktrace2(DISPATCH_QOS_TRACE_queue_creation_start,
dq->dq_serialnum,
_dispatch_priority_to_pp_prefer_fallback(dq->dq_priority));
_dispatch_ktrace4(DISPATCH_QOS_TRACE_queue_creation_end,
dq_label[0], dq_label[1], dq_label[2], dq_label[3]);
});
return _dispatch_introspection_queue_create(dqu);
}
在_dispatch_trace_queue_create
源码中,主要分两部分,一部分是系统调试,另一部分是调用_dispatch_introspection_queue_create
函数。调试就不看了,下面我们就来看看_dispatch_introspection_queue_create
函数的内容。
_dispatch_introspection_queue_create源码:
dispatch_queue_class_t
_dispatch_introspection_queue_create(dispatch_queue_t dq)
{
dispatch_queue_introspection_context_t dqic;
size_t sz = sizeof(struct dispatch_queue_introspection_context_s);
if (!_dispatch_introspection.debug_queue_inversions) {
sz = offsetof(struct dispatch_queue_introspection_context_s,
__dqic_no_queue_inversion);
}
dqic = _dispatch_calloc(1, sz);
dqic->dqic_queue._dq = dq;
if (_dispatch_introspection.debug_queue_inversions) {
LIST_INIT(&dqic->dqic_order_top_head);
LIST_INIT(&dqic->dqic_order_bottom_head);
}
dq->do_finalizer = dqic;
_dispatch_unfair_lock_lock(&_dispatch_introspection.queues_lock);
LIST_INSERT_HEAD(&_dispatch_introspection.queues, dqic, dqic_list);
_dispatch_unfair_lock_unlock(&_dispatch_introspection.queues_lock);
DISPATCH_INTROSPECTION_INTERPOSABLE_HOOK_CALLOUT(queue_create, dq);
if (DISPATCH_INTROSPECTION_HOOK_ENABLED(queue_create)) {
_dispatch_introspection_queue_create_hook(dq);
}
return upcast(dq)._dqu;
}
dispatch_queue_introspection_context_t & dispatch_queue_introspection_context_s 结构体
typedef struct dispatch_queue_introspection_context_s {
dispatch_queue_class_t dqic_queue;
dispatch_function_t dqic_finalizer;
LIST_ENTRY(dispatch_queue_introspection_context_s) dqic_list;
char __dqic_no_queue_inversion[0];
// used for queue inversion debugging only
dispatch_unfair_lock_s dqic_order_top_head_lock;
dispatch_unfair_lock_s dqic_order_bottom_head_lock;
LIST_HEAD(, dispatch_queue_order_entry_s) dqic_order_top_head;
LIST_HEAD(, dispatch_queue_order_entry_s) dqic_order_bottom_head;
} *dispatch_queue_introspection_context_t;
在_dispatch_introspection_queue_create
函数中
- 首先定义了一个
dispatch_queue_introspection_context_t
的上下文。代码也放在上面了。 - 然后获取了
dispatch_queue_introspection_context_s
结构体占用内存的大小。 - 然后判断非自我观察调试队列倒置(翻译过来的,我也不知道啥意思),反正就是这种情况下对上一步取出的
sz
偏移 - 然后调用
_dispatch_calloc
函数分配内存 - 将传入的
dq
赋值给一开始定义的dqic
的dqic_queue._dq
- 还是3中的那个判断,开始监听队列的头尾(这里应该是入队和出队)时添加任务到队列尾,从队列头取出任务开始执行(个人猜想)
- 将
dqic
赋值给传入的dq
的do_finalizer
(终结器,应该是终止队列时使用的) - 加锁插入创建的队列到系统管理的队列里面?在解锁
- 一些hook处理(不是很明白)
- 返回
由于能力有限,此处分析的乱七八糟,还请大神们指点。,其实到这里也就基本分析完了队列的整个创建过程,在不同操作系统,对于不同架构的CPU,队列的差别肯定是有的,但是原理应该是一样的。感觉分析libdispath
的源码还是挺难的,比较队列线程这种东西属于很底层的东西了。
3. 一些定义
3.1 dispatch_queue_t
分析了半天,其实我们还没有仔细的分析dispatch_queue_t
到底是个什么东西呢 ?
我们点击跳转后跳转到DISPATCH_DECL(dispatch_queue);
这样一行代码处,这显然不是我们想要的。这个时候我们可以看看我们的创建队列的方法时返回值处的代码return _dispatch_trace_queue_create(dq)._dq;
这个_dq
就是我们要返回的值,前面的_dispatch_trace_queue_create
函数的返回值类型是dispatch_queue_class_t
定义如下:
// Dispatch queue cluster class: type for any dispatch_queue_t
typedef union {
struct dispatch_queue_s *_dq;
struct dispatch_workloop_s *_dwl;
struct dispatch_lane_s *_dl;
struct dispatch_queue_static_s *_dsq;
struct dispatch_queue_global_s *_dgq;
struct dispatch_queue_pthread_root_s *_dpq;
struct dispatch_source_s *_ds;
struct dispatch_channel_s *_dch;
struct dispatch_mach_s *_dm;
dispatch_lane_class_t _dlu;
#ifdef __OBJC__
id _objc_dq;
#endif
} dispatch_queue_class_t DISPATCH_TRANSPARENT_UNION;
这是个联合体,成员_dq
是dispatch_queue_s
类型,其定义如下:
struct dispatch_queue_s {
DISPATCH_QUEUE_CLASS_HEADER(queue, void *__dq_opaque1);
/* 32bit hole on LP64 */
} DISPATCH_ATOMIC64_ALIGN;
接下来我们继续看DISPATCH_QUEUE_CLASS_HEADER
,这是个宏,定义如下:
#define DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
_DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__); \
/* LP64 global queue cacheline boundary */ \
unsigned long dq_serialnum; \
const char *dq_label; \
DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \
const uint16_t dq_width, \
const uint16_t __dq_opaque2 \
); \
dispatch_priority_t dq_priority; \
union { \
struct dispatch_queue_specific_head_s *dq_specific_head; \
struct dispatch_source_refs_s *ds_refs; \
struct dispatch_timer_source_refs_s *ds_timer_refs; \
struct dispatch_mach_recv_refs_s *dm_recv_refs; \
struct dispatch_channel_callbacks_s const *dch_callbacks; \
}; \
int volatile dq_sref_cnt
这个宏定义了一些通用属性,包含队列的所有成员,例如编号、label
、队列宽度、优先级等等。
那么回到问题本身,我们的dispatch_queue_t
是在什么时候定义的呢?我们尝试搜索了一下dispatch_queue_t;
(其实也搜索了dispatch_source_s
前后都有空格)找到如下代码:
typedef struct dispatch_continuation_s *dispatch_continuation_t;
typedef struct dispatch_queue_s *dispatch_queue_t;
typedef struct dispatch_source_s *dispatch_source_t;
typedef struct dispatch_group_s *dispatch_group_t;
typedef struct dispatch_object_s *dispatch_object_t;
在这里我们可以清楚的看到dispatch_queue_t
是一个dispatch_queue_s
类型的结构体指针。至此我们就找到了dispatch_queue_t
的定义。
3.2 dispatch_object_t
在探索过程中我们还多次看到了dispatch_object_t
的身影,其实它的定义就在dispatch_queue_class_t
的下面,如果我们直接搜索dispatch_object_t
会看到很多关于它的定义,为什么我会认为dispatch_queue_class_t
下面代码处使我们想要的呢?因为有个#ifndef __OBJC__
dispatch_object_t定义:
typedef union {
struct _os_object_s *_os_obj;
struct dispatch_object_s *_do;
struct dispatch_queue_s *_dq;
struct dispatch_queue_attr_s *_dqa;
struct dispatch_group_s *_dg;
struct dispatch_source_s *_ds;
struct dispatch_channel_s *_dch;
struct dispatch_mach_s *_dm;
struct dispatch_mach_msg_s *_dmsg;
struct dispatch_semaphore_s *_dsema;
struct dispatch_data_s *_ddata;
struct dispatch_io_s *_dchannel;
struct dispatch_continuation_s *_dc;
struct dispatch_sync_context_s *_dsc;
struct dispatch_operation_s *_doperation;
struct dispatch_disk_s *_ddisk;
struct dispatch_workloop_s *_dwl;
struct dispatch_lane_s *_dl;
struct dispatch_queue_static_s *_dsq;
struct dispatch_queue_global_s *_dgq;
struct dispatch_queue_pthread_root_s *_dpq;
dispatch_queue_class_t _dqu;
dispatch_lane_class_t _dlu;
uintptr_t _do_value;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;
我们发现dispatch_object_t
是一个联合体,使用联合体的成员互斥性,同时只存在一种类型带来了多态性。我们可以用一个dispatch_object_t
在GCD
中表示所有的类型。
4. 总结
至此我们的GCD队列篇就分析完毕了下面我们稍作总结
- 在iOS中可以获取主队列,全局并行队列,还可以自己创建队列,自己创建队列可以是串行的也可以是并行;
- 主队列和串行队列的宽度都是1,全局并行队列的宽度是0x1000-1 = 0xfff,自己创建的并行队列的宽度是0x1000-2 = 0xffe;
- 主队列底层是由
DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q)
获取到的,暂时没有找到其开源的具体实现 - 全局并发队列是从
_dispatch_root_queues
数组中取出的 - 关于自我创建的队列也是从
_dispatch_root_queues
数组中取出一个对应的值,赋值给tq
,然后创建响应的队列 - 关于
_dispatch_root_queues
数组中的队列是在_dispatch_introspection_init
函数中循环遍历创建的,_dispatch_introspection_init
是由libdispatch_init
函数调用的。