导语:在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)
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方案也到达了最大线程并发数的目的。
三、小结
在iOS开发中,我们常将耗时任务提交给GCD的并发队列,但是并发队列并不会去管理最大并发数,无限制提交任务给并发队列,会给性能带来问题。
YYKit组件中的YYDispatchQueuePool 也能控制并发队列的并发数;其思路是为不同优先级创建和 CPU 数量相同的 serial queue,每次从 pool 中获取 queue 时,会轮询返回其中一个 queue。
QSDispatchQueue是使用信号量让并发队列中的任务并发数得到抑制;YYDispatchQueuePool是让一定数量的串行队列代替并发队列,避开了并发队列不好控制并发数的问题。
End
作者:南华coder
链接:http://www.jianshu.com/p/5d51a367ed62
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。