在GCD(Grand Central Dispatch)中,dispatch_get_global_queue用于获取全局并发队列。全局并发队列在应用程序的生命周期内都存在,它们由系统管理并且共享给所有应用程序使用。

全局并发队列实际上只有三个(在iOS 8之前是四个),分别对应不同的优先级:高、默认、低(以及在iOS 8之前的后台)。当你使用dispatch_get_global_queue函数获取队列时,实际上是在获取这三个全局队列之一。

因此,如果突发的全局过度使用dispatch_get_global_queue,就可能会有很多任务都在这三个全局队列上执行,可能会内部会导致创建过多的线程(每个队列 64 个)创建调度的开销等相反可能会浪费 CPU 算力。

在 YYText库YYTextAsyncLayer文件中,负责渲染的 queue 运行在这样的一段代码创建的 queue 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/// Global display queue, used for content rendering.
static dispatch_queue_t YYTextAsyncLayerGetDisplayQueue() {
#define MAX_QUEUE_COUNT 16
static int queueCount;
static dispatch_queue_t queues[MAX_QUEUE_COUNT];
static dispatch_once_t onceToken;
static int32_t counter = 0;
dispatch_once(&onceToken, ^{
queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
for (NSUInteger i = 0; i < queueCount; i++) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
queues[i] = dispatch_queue_create("com.ibireme.text.render", attr);
}
} else {
for (NSUInteger i = 0; i < queueCount; i++) {
queues[i] = dispatch_queue_create("com.ibireme.text.render", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
}
}
});
uint32_t cur = (uint32_t)OSAtomicIncrement32(&counter);
return queues[(cur) % queueCount];
#undef MAX_QUEUE_COUNT
}

注:当获取一个 render queue 的时候,创建了一个queues的数组。数组中填充queue个数根据核心数以及指定最大定义个数MAX_QUEUE_COUNT约束。 然后每次获取的时候会自增counter,循环获取。

YYDispatchQueuePool在上面代码上做了进一步加工:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static YYDispatchContext *YYDispatchContextGetForQOS(NSQualityOfService qos) {
static YYDispatchContext *context[5] = {0};
switch (qos) {
case NSQualityOfServiceUserInteractive: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[0] = YYDispatchContextCreate("com.ibireme.yykit.user-interactive", count, qos);
});
return context[0];
} break;
case NSQualityOfServiceUserInitiated: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[1] = YYDispatchContextCreate("com.ibireme.yykit.user-initiated", count, qos);
});
return context[1];
} break;
case NSQualityOfServiceUtility: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[2] = YYDispatchContextCreate("com.ibireme.yykit.utility", count, qos);
});
return context[2];
} break;
case NSQualityOfServiceBackground: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[3] = YYDispatchContextCreate("com.ibireme.yykit.background", count, qos);
});
return context[3];
} break;
case NSQualityOfServiceDefault:
default: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[4] = YYDispatchContextCreate("com.ibireme.yykit.default", count, qos);
});
return context[4];
} break;
}
}

注:针对某个指定优先级创建内核相当数量的 queue 保存起来。另外:一个串行队列在其生命周期中可能会使用多个线程。但是,无论使用多少个线程,它都会保证在任何给定的时间点,只有一个任务在执行。(队列和线程是两个概念)。

源码总结:

  1. 队列池的创建: YYDispatchQueuePool在初始化时会根据传入的参数创建一定数量的串行队列,并将这些队列存入一个数组中。数量一般为当前设备的处理器核心数。

  2. 队列的获取: 当你从YYDispatchQueuePool获取队列时,它会从创建的队列数组中按照轮询的方式返回一个队列。这样可以保证每个队列被均匀地使用,避免某个队列上任务过多,从而提高了多核处理器的利用率。

  3. 线程优先级: YYDispatchQueuePool提供了根据优先级获取队列的接口,不同优先级的队列池管理的队列优先级也不同。这样可以根据任务的重要性选择不同优先级的队列。

  4. 线程复用: YYDispatchQueuePool中的队列在执行完任务后不会立即被销毁,而是可以被复用。这样避免了反复创建和销毁线程带来的开销。

思考:YYDispatchQueuePool处理任务效率一定比dispatch_get_global_queue高吗?

不能一概而论,我们最终的目的是任务消耗算力占比不能太大的情况下(需要给主线程留空间)尽可能高效的处理任务。如果你的任务数量和核心数相当,另外任务需要的耗时相当,那么使用YYDispatchQueuePool更合适(比如 yylabelrender 任务)。任务量少和使用dispatch_get_global_queue没有效率区别。如果分配的任务耗时长短不一也不太适合YYDispatchQueuePool,因为任务可能被串行队列阻塞,造成cpu浪费。如果任务量瞬时太多,可以通过YYDispatchQueuePool进行平抑。