在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
| 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 保存起来。另外:一个串行队列在其生命周期中可能会使用多个线程。但是,无论使用多少个线程,它都会保证在任何给定的时间点,只有一个任务在执行。(队列和线程是两个概念)。
源码总结:
队列池的创建: YYDispatchQueuePool
在初始化时会根据传入的参数创建一定数量的串行队列,并将这些队列存入一个数组中。数量一般为当前设备的处理器核心数。
队列的获取: 当你从YYDispatchQueuePool
获取队列时,它会从创建的队列数组中按照轮询的方式返回一个队列。这样可以保证每个队列被均匀地使用,避免某个队列上任务过多,从而提高了多核处理器的利用率。
线程优先级: YYDispatchQueuePool
提供了根据优先级获取队列的接口,不同优先级的队列池管理的队列优先级也不同。这样可以根据任务的重要性选择不同优先级的队列。
线程复用: YYDispatchQueuePool
中的队列在执行完任务后不会立即被销毁,而是可以被复用。这样避免了反复创建和销毁线程带来的开销。
思考:YYDispatchQueuePool
处理任务效率一定比dispatch_get_global_queue
高吗?
不能一概而论,我们最终的目的是任务消耗算力占比不能太大的情况下(需要给主线程留空间)尽可能高效的处理任务。如果你的任务数量和核心数相当,另外任务需要的耗时相当,那么使用YYDispatchQueuePool
更合适(比如 yylabel
的 render
任务)。任务量少和使用dispatch_get_global_queue
没有效率区别。如果分配的任务耗时长短不一也不太适合YYDispatchQueuePool
,因为任务可能被串行队列阻塞,造成cpu浪费。如果任务量瞬时太多,可以通过YYDispatchQueuePool
进行平抑。