背景
主要还是学习做笔记,并且锻炼下写文章能力,以及挽回我的flag T T,如有错,请评论指正
ibireme大佬在iOS 保持界面流畅的技巧 原文:
使用 concurrent queue 时不可避免会遇到这种问题,但使用 serial queue 又不能充分利用多核 CPU 的资源。我写了一个简单的工具 YYDispatchQueuePool,为不同优先级创建和 CPU 数量相同的 serial queue,每次从 pool 中获取 queue 时,会轮询返回其中一个 queue。我把 App 内所有异步操作,包括图像解码、对象释放、异步绘制等,都按优先级不同放入了全局的 serial queue 中执行,这样尽量避免了过多线程导致的性能问题。
作为YYKit的其中一个组件,也可以说相当精简,因此学习一下 在并发编程过程中会遇到两种任务:
- CPU密集型task
- IO密集型task
一般情况下,都是IO密集型的比较多,因此都会产生比较多的CPU空闲时间,而且现在多核的CPU可以实现并行的任务调度,如果不使用起来就会很浪费。
那这里为什么会提及的CPU密集型的,就是如果像上面提及的,在并发的过程中,创建过多的线程,导致线程的处理逼近CPU的处理的话,还是会导致性能下降。CPU通过时间片轮转等调度算法,实现伪并行(多核下还是能实现真并行)。
因此作者提出,通过自行控制queue的数量,将线程数控制到一个合理的数量(这里的合理数量就是,每个优先级的线程数保持和CPU核心数相同)
调用方式
首先先看调用方式简单了解
YYDispatchQueuePool *pool = [YYDispatchQueuePool defaultPoolForQOS:NSQualityOfServiceUtility];
dispatch_queue_t queue = [pool queue];
// 使用 dispatch_queue_t
dispatch_queue_t queue = [pool queue];
dispatch_async(queue, ^{
NSLog(@"测试");
});
自定义创建
/**
创建一个dispatch_queuet_t的pool对象
@param name 队列池的名字
@param queueCount 队列的数量控制,最多32
@param qos 队列的优先级
*/
- (instancetype)initWithName:(NSString *)name
queueCount:(NSUInteger)queueCount
qos:(NSQualityOfService)qos;
只通过优先级获取(推荐使用)
/**
获取默认创建的pool对象
@param qos 队列的优先级
*/
+ (instancetype)defaultPoolForQOS:(NSQualityOfService)qos;
为什么会推荐这个呢?这里主要就是已经帮你创建好对应的线程控制,如果你没什么特殊需求,直接使用即可。
实现细节
QoS(Quality of Service,服务质量)
用来代表服务的优先级
iOS8之前,优先级各有各的方式
NSOperation 和 NSThread 都通过 threadPriority 来指定优先级
GCD也只能设置
- DISPATCH_QUEUE_PRIORITY_HIGH
- DISPATCH_QUEUE_PRIORITY_LOW
- DISPATCH_QUEUE_PRIORITY_BACKGROUND
- DISPATCH_QUEUE_PRIORITY_DEFAULT
在iOS8之后 统一为5个等级
- NSQualityOfServiceUserInteractive:最高优先级, 用于处理 UI 相关的任务
- NSQualityOfServiceUserInitiated:次高优先级, 用于执行需要立即返回的任务
- NSQualityOfServiceUtility:普通优先级,主要用于不需要立即返回的任务
- NSQualityOfServiceBackground:后台优先级,用于处理一些用户不会感知的任务
- NSQualityOfServiceDefault:默认优先级,当没有设置优先级的时候,线程默认优先级
GCD的则提供对应的
- QOS_CLASS_USER_INTERACTIVE
- QOS_CLASS_USER_INITIATED
- QOS_CLASS_UTILITY
- QOS_CLASS_BACKGROUND
- QOS_CLASS_DEFAULT
- QOS_CLASS_UNSPECIFIED
创建Context
Context一个结构体,用于记录上下文数据
typedef struct {
const char *name;
void **queues;
uint32_t queueCount;
int32_t counter; // 计数器
} YYDispatchContext;
需要关注的是计数器,这个计数器是干嘛的?
一个context有n个queue,那我每次返回哪一个呢?
这里的逻辑是:
我轮着返回给你啊~
// 这个[pool queue],就是你主要使用的方法,获取一个可以用的队列防止任务
- (dispatch_queue_t)queue {
return YYDispatchContextGetQueue(_context);
}
static dispatch_queue_t YYDispatchContextGetQueue(YYDispatchContext *context) {
// 计算器++ (调用一次,我就拿下一个~)
uint32_t counter = (uint32_t)OSAtomicIncrement32(&context->counter);
// 取出队列
void *queue = context->queues[counter % context->queueCount];
// 返回队列
return (__bridge dispatch_queue_t)(queue);
}
Context创建代码注释
static YYDispatchContext *YYDispatchContextCreate(const char *name,
uint32_t queueCount,
NSQualityOfService qos) {
// 创建上下文,用于记录
YYDispatchContext *context = calloc(1, sizeof(YYDispatchContext));
if (!context) return NULL;
// 创建指针数组
context->queues = calloc(queueCount, sizeof(void *));
if (!context->queues) {
free(context);
return NULL;
}
if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
// 这个方法设置Qos只在8.0之后进行统一,不过还是需要转换一下枚举
dispatch_qos_class_t qosClass = NSQualityOfServiceToQOSClass(qos);
// 根据队列的数量进行创建
for (NSUInteger i = 0; i < queueCount; i++) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, qosClass, 0);
dispatch_queue_t queue = dispatch_queue_create(name, attr);
context->queues[i] = (__bridge_retained void *)(queue);
}
} else {
// 获取Qos和GCD的对应关系
long identifier = NSQualityOfServiceToDispatchPriority(qos);
for (NSUInteger i = 0; i < queueCount; i++) {
// 通过和全局队列的优先级设置对应的方式来设置
dispatch_queue_t queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL);
// queue的优先级和dispatch_get_global_queue(identifier, 0)优先级一致
dispatch_set_target_queue(queue, dispatch_get_global_queue(identifier, 0));
context->queues[i] = (__bridge_retained void *)(queue);
}
}
context->queueCount = queueCount;
if (name) {
context->name = strdup(name);
}
return context;
}
通过Qos进行获取queue
为什么推荐通过Qos直接获取队列?当然是因为提供的便利控制数量的方法
通过 YYDispatchContextGetForQOS
方法去获取一个文
+ (instancetype)defaultPoolForQOS:(NSQualityOfService)qos {
// 创建数量为5的context数组,为啥是5?因为有5个优先级~
// 一人一个坑
static YYDispatchContext *context[5] = {0};
switch (qos) {
case NSQualityOfServiceUserInteractive: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// activeProcessorCount 代表进程中激活的核心数量
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
// 这里有5个context,按顺序存入即可
context[0] = YYDispatchContextCreate("com.ibireme.yykit.user-interactive", count, qos);
});
return context[0];
} break;
case NSQualityOfServiceUserInitiated:
case NSQualityOfServiceUtility:
//...
从下图可以看出,不断的进行塞入任务,一个优先级只会创建四条线程(因为只有创建了4条serial queue)
以此类推,5个优先级,最多就是20条线程,控制住了线程原地爆炸的情况
总结
大体上原理,就是通过创建不同优先级的context,context中创建有限数量的queue。
在使用的时候,通过优先级获取不同的context,然后轮着返回不同的queue让你换着用,你就可以一定程度上实现并行队列,并且控制线程数量。
所以这个库来说,可以应用的范围也挺广的,在你频繁进行操作线程的地方,你都可以使用,如果你觉得自动创建的queue数量太少,那你自定义吧~
吐槽
为什么掘金不支持粘贴图片上传!!!!!!!!!!