YYDispatchQueuePool 源码阅读笔记
iOS 保持界面流畅的技巧
http://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
大量的任务提交到后台队列时,某些任务会因为某些原因(此处是 CGFont 锁)被锁住导致线程休眠,或者被阻塞,concurrent queue 随后会创建新的线程来执行其他任务。当这种情况变多时,或者 App 中使用了大量 concurrent queue 来执行较多任务时,App 在同一时刻就会存在几十个线程同时运行、创建、销毁。CPU 是用时间片轮转来实现线程并发的,尽管 concurrent queue 能控制线程的优先级,但当大量线程同时创建运行销毁时,这些操作仍然会挤占掉主线程的 CPU 资源。ASDK 有个 Feed 列表的 Demo:SocialAppLayout
,当列表内 Cell 过多,并且非常快速的滑动时,界面仍然会出现少量卡顿,我谨慎的猜测可能与这个问题有关。
使用 concurrent queue 时不可避免会遇到这种问题,但使用 serial queue 又不能充分利用多核 CPU 的资源。我写了一个简单的工具 YYDispatchQueuePool,为不同优先级创建和 CPU 数量相同的 serial queue,每次从 pool 中获取 queue 时,会轮询
返回其中一个 queue。我把 App 内所有异步操作,包括图像解码、对象释放、异步绘制等,都按优先级不同放入了全局的 serial queue 中执行,这样尽量避免了过多线程导致的性能问题。
通过 YYDispatchQueuePool 进行管理,控制了 App 总线程数量。
服务质量优先级的标识符NSQualityOfService(qos)
的和GCD队列优先级的对应关系
从队列池中取队列
YYDispatchQueuePool 源码阅读笔记
http://www.kittenyang.com/yydispatchqueuepool-learning-note/
还有其他参考链接:
iOS 全局并发队列管理工具。YYDispatchQueuePool
用法
// 1、从全局的 queue pool 中获取一个 queue
dispatch_queue_t queue = YYDispatchQueueGetForQOS(NSQualityOfServiceUtility);
// 2、创建一个新的 serial queue pool
YYDispatchQueuePool *pool = [[YYDispatchQueuePool alloc] initWithName:@"file.read" queueCount:5 qos:NSQualityOfServiceBackground];
dispatch_queue_t queue = [pool queue];
首先明确几个函数:
- 输入一个
NSQualityOfService qos
,输出一个YYDispatchContext
- 输入当前的
context
,输出一个队列dispatch_queue_t
- 输入
NSQualityOfService
,输出qos_class_t
- 输入
NSQualityOfService
,输出dispatch_queue_priority_t
- 输入原始信息
const char *name,uint32_t queueCount,NSQualityOfService qos)
,创建一个YYDispatchContext
NSQualityOfService
有五个可选值:
NSQualityOfServiceUserInteractive
NSQualityOfServiceUserInitiated
NSQualityOfServiceUtility
NSQualityOfServiceBackground
NSQualityOfServiceDefault
实现步骤
步骤1:
+ (instancetype)defaultPoolForQOS:(NSQualityOfService)qos
中判断 qos 类型: 根据不同的 qos 类型创建一个单例。创建通过 - (instancetype)initWithContext:(YYDispatchContext *)context
方法。
步骤2:
- (instancetype)initWithContext:(YYDispatchContext *)context
需要一个结构体 YYDispatchContext
typedef struct{
const char *name;
void **queues;
uint32_t queueCount;
int32_t counter;
}YYDispatchContext;
参数 context
通过 static YYDispatchContext *YYDispatchContextGetForQOS (NSQualityOfService qos)
方法创建
步骤3:
static YYDispatchContext *YYDispatchContextGetForQOS(NSQualityOfService qos)
中遍历 qos ;
疑问1:为什么这里还要
dispatch_once
?外层的函数已经能保证只执行一次了。疑问2:为什么要创建一个临时数组 ——
YYDispatchContext *context[5] = {0}
,switch 会且只会匹配到一个 case 。
context
实例通过 static YYDispatchContext *YYDispatchContextCreate(const char *name,uint32_t queueCount, NSQualityOfService qos)
创建。
步骤4:
static YYDispatchContext *YYDispatchContextCreate(const char *name,uint32_t queueCount, NSQualityOfService qos)
创建 context
最重要的是创建 queues
,并且 queues
具有和 qos 匹配的优先级,对应关系为:
NSQualityOfServiceUserInteractive —- DISPATCH_QUEUE_PRIORITY_HIGH
NSQualityOfServiceUserInitiated —- DISPATCH_QUEUE_PRIORITY_HIGH
NSQualityOfServiceUtility —- DISPATCH_QUEUE_PRIORITY_LOW
NSQualityOfServiceBackground —- DISPATCH_QUEUE_PRIORITY_BACKGROUND
NSQualityOfServiceDefault —- DISPATCH_QUEUE_PRIORITY_DEFAULT
首先创建队列无非是用 dispatch_queue_create
, 所以核心函数就是 dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
需要指定一个标识符 label
和 attr
, attr
可以是 DISPATCH_QUEUE_SERIAL
或者 DISPATCH_QUEUE_CONCURRENT
。
但是如何指定优先级就有区别呢?
a. 在8.0之前:
先直接创建一个串行队列 dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL);
再通过 dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
这个函数可以改变object的优先级与目标queue相同,第一个参数为要设置优先级的queue,第二个参数是参照 物,既将第一个queue的优先级和第二个queue的优先级设置一样而服务质量优先级的标识符 qos 的和队列优先级的对应关系上面已经提到:
NSQualityOfServiceUserInteractive —- DISPATCH_QUEUE_PRIORITY_HIGH
NSQualityOfServiceUserInitiated —- DISPATCH_QUEUE_PRIORITY_HIGH
NSQualityOfServiceUtility —- DISPATCH_QUEUE_PRIORITY_LOW
NSQualityOfServiceBackground —- DISPATCH_QUEUE_PRIORITY_BACKGROUND
NSQualityOfServiceDefault —- DISPATCH_QUEUE_PRIORITY_DEFAULT
b.在8.0以后
就比较简单了,可以通过这个方法: dispatch_queue_attr_make_with_qos_class(dispatch_queue_attr_t attr, dispatch_qos_class_t qos_class, int relative_priority);
设置队列优先级,qos 和 qos_class 的对应关系为:
NSQualityOfServiceUserInteractive: return QOS_CLASS_USER_INTERACTIVE;
NSQualityOfServiceUserInitiated: return QOS_CLASS_USER_INITIATED;
NSQualityOfServiceUtility: return QOS_CLASS_USER_INITIATED;
NSQualityOfServiceBackground: return QOS_CLASS_BACKGROUND;
NSQualityOfServiceDefault: return QOS_CLASS_DEFAULT;
default: return QOS_CLASS_UNSPECIFIED;
回到 步骤3,继续。
context 创建完成。
回到 步骤1, YYDispatchQueuePool
实例创建完成.
这就是+ (instancetype)defaultPoolForQOS:(NSQualityOfService)qos
经历的完整过程。
当我们需要从队列池中取队列是,调用 -(dispatch_queue_t)queue;
规则是: 根据当前线程池的上下文信息也就是 context
static dispatch_queue_t YYDispatchContextGetQueue(YYDispatchContext *context) {
int32_t counter = OSAtomicIncrement32(&context->counter); // 为了更安全地递增一个全局计数器,我们使用 OSAtomicIncrement32 来原子操作级别地增加 counter 数
if (counter < 0) counter = -counter;
void *queue = context->queues[counter % context->queueCount];
return (__bridge dispatch_queue_t)(queue);
}