iOS实录:GCD使用小结(二)

2017-07-29iOS开发

导语:在GCD的使用中,需要考虑控制最大并发数 和 线程同步这两个问题,本文主要介绍GCD中如何控制最大并发数 。

一、概述

1、GCD并发的困扰

在GCD中有两种队列,分别是串行队列和并发队列。在串行队列中,同一时间只有一个任务在执行,不能充分利用多核 CPU 的资源,效率较低。

并发队列可以分配多个线程,同时处理不同的任务;效率虽然提升了,但是多线程的并发是用时间片轮转方法实现的,线程创建、销毁、上下文切换等会消耗CPU 资源。

目前iPhone的处理器是多核(2个、4个),适当的并发可以提高效率,但是无节制地并发,如将大量任务不加思索就用并发队列来执行,这只会大量增加线程数,抢占CPU资源,甚至会挤占掉主线程的 CPU 资源(极端情况)。

此外,提交给并发队列的任务中,有些任务内部会有全局的锁(如 CoreText 绘制时的 CGFont 内部锁),会导致线程休眠、阻塞;一旦这类任务多,并发队列还需要创建新的线程来执行其他任务;这种情况下,线程数大量增加是避免不了的。

2、优雅的NSOperationQueue

NSOperationQueue是iOS提供的工作队列,开发者只需要将任务封装在NSOperation的子类(NSBlockOperation、NSInvocationOperation或自定义NSOperation子类)中,然后添加进NSOperationQueue队列,队列就会按照优先顺序及工作的从属依赖关系(如果有的话)组织执行。

NSOperationQueue中,已经考虑到了最大并发数的问题,并提供了maxConcurrentOperationCount属性设置最大并发数(该属性需要在任务添加到队列中之前进行设置)。maxConcurrentOperationCount默认值是-1;如果值设为0,那么不会执行任何任务;如果值设为1,那么该队列是串行的;如果大于1,那么是并行的。

NSOperationQueue *queue = [[NSOperationQueue alloc]init];queue.maxConcurrentOperationCount = 2;//添加Operation任务...

第三方库如SDWebImage库和AFNetworking 中就是采用NSOperationQueue来控制最大并发数的。

说明:NSOperationQueue使用详见多线程编程3 - NSOperationQueue 和 NSOperation

3、我们该怎么办

GCD多线程方案很优秀,在iOS 4 与 MacOS X 10.6之后,NSOperationQueue的底层就是用GCD来实现的。

NSOperationQueue在控制最大并发数上的确很方便,但是GCD也提供了某些机制可以实现控制最大并发数的效果。

开发中NSOperationQueue和GCD都可以用,视场景而定(个人更喜欢用GCD)。

二、QSDispatchQueue方案

1、GCD的信号量机制(dispatch_semaphore)

信号量是一个整型值,有初始计数值;可以接收通知信号和等待信号。当信号量收到通知信号时,计数+1;当信号量收到等待信号时,计数-1;如果信号量为0,线程会被阻塞,直到信号量大于0,才会继续下去。

使用信号量机制可以实现线程的同步,也可以控制最大并发数。以下是如何控制最大并发数的代码。

dispatch_queue_t workConcurrentQueue = dispatch_queue_create(

"cccccccc", DISPATCH_QUEUE_CONCURRENT);dispatch_queue_t serialQueue

= dispatch_queue_create("sssssssss",DISPATCH_QUEUE_SERIAL);dispatch

_semaphore_t semaphore = dispatch_semaphore_create(3);for (NSInteger

i = 0; i < 10; i++) {  dispatch_async(serialQueue, ^{      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);      dispatch_async(workConcurrentQueue, ^{          NSLog(@"thread-info:%@开始执行任务%d",[NSThread

currentThread],(int)i);          sleep(1);          NSLog(@"thread-info:%@结束执行任务%d",[NSThread

currentThread],(int)i);          dispatch_semaphore_signal(semaphore);});  });}NSLog(@"主线程...!");

说明:从执行结果中可以看出,虽然将10个任务都异步加入了并发队列,但是信号量机制控制了最大线程并发数,始终是3个线程在执行任务。此外,这些任务也没有阻塞主线程。

2、QSDispatchQueue方案的实现

1)直接在代码中使用GCD的信号量,不够优雅,代码也很冗余;基于此,QSDispatchQueue方案出来了。(代码很简单,一共两个文件)

2)QSDispatchQueue方法声明如下:

//QSDispatchQueue.h@interface QSDispatchQueue : NSObject#pragma mark

- main queue + global queue/** 全局并发队列的最大并发数,默认4 */+ (QSDispatchQueue *)mainThreadQueue;+ (QSDispatchQueue *)defaultGlobalQueue;+ (QSDispatchQueue *)lowGlobalQueue;+ (QSDispatchQueue *)highGlobalQueue;+ (QSDispatchQueue *)backGroundGlobalQueue;#pragma mark -@property

(nonatomic,assign,readonly)NSUInteger concurrentCount;- (instancetype)init;/** 默认最大并发数是1 @param queue 并发队列 */- (instancetype)initWithQueue:(dispatch_queue_t)queue;/** @param queue 并发队列 @param concurrentCount 最大并发数,应大于1 */- (instancetype)initWithQueue:(dispatch_queue_t)queue

concurrentCount:(NSUInteger)concurrentCount;//同步-

(void)

sync:(dispatch_block_t)block;//异步- (void)async:

(dispatch

_block_t)block;@end

3、QSDispatchQueue方案的使用

dispatch_queue_t workConcurrentQueue = dispatch_queue_create(

"cccccccc",

DISPATCH_QUEUE_CONCURRENT);

QSDispatchQueue *queue = [[QSDispatchQueue alloc]initWithQueue:

workConcurrentQueue concurrentCount:3];for (NSInteger i = 0; i < 10;

i++) {    [queue async:^{        NSLog(@"thread-info:%@开始执行任务%d",

[NSThread currentThread],(int)i);        sleep(1);        NSLog(@"thread-info:%@结束执行任务%d",

[NSThread currentThread],(int)i);    }];}NSLog(@"主线程任务...");

执行结果如下图:

说明:从执行结果中来看,通过QSDispatchQueue方案也到达了最大线程并发数的目的。

使用QSDispatchQueue方案,代码更简洁,让开发者不用去时刻注意信号量的处理,只关注任务即可。

三、小结

在iOS开发中,我们常将耗时任务提交给GCD的并发队列,但是并发队列并不会去管理最大并发数,无限制提交任务给并发队列,会给性能带来问题。

YYKit组件中的YYDispatchQueuePool也能控制并发队列的并发数;其思路是为不同优先级创建和 CPU 数量相同的 serial queue,每次从 pool 中获取 queue 时,会轮询返回其中一个 queue。

QSDispatchQueue是使用信号量让并发队列中的任务并发数得到抑制;YYDispatchQueuePool是让一定数量的串行队列代替并发队列,避开了并发队列不好控制并发数的问题。

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

推荐阅读更多精彩内容