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); 需要指定一个标识符 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);
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容