YYKit/UtilityiOS夯实基础

YYDispatchQueuePool 源码阅读笔记

2017-01-17  本文已影响87人  kakukeme

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 有五个可选值:

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); 需要指定一个标识符 labelattr, 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 创建完成。

回到 步骤1YYDispatchQueuePool 实例创建完成.

这就是+ (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);
}

上一篇下一篇

猜你喜欢

热点阅读