【转】并发编程之Operation Queue
http://blog.xcodev.com/blog/2013/10/28/operation-queue-intro/
随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双核、甚至四核时代。如何充分发挥这些CPU的性能,会变得越来越重要。在iOS中如果想要充分利用多核心CPU的优势,就要采用并发编程,提高CPU的利用率。iOS中并发编程中主要有2种方式Operation Queue和GCD(Grand Central Dispatch)。下面就来先来说一下Operation Queue。
异步调用和并发
在深入之前,首先说说异步调用和并发。这两个概念在并发编程中很容易弄混淆。异步调用是指调用时无需等待结果返回的调用,异步调用往往会触发后台线程处理,比如NSURLConnection的异步网络回调。并发是指多个任务(线程)同时执行。在异步调用的实现中往往采用并发机制,然而并不是所有异步都是并发机制,也有可能是其他机制,比如一些依靠中断进行的操作。
为什么Operation Queue
Operation Queue提供一个面向对象的并发编程接口,支持并发数,线程优先级,任务优先级,任务依赖关系等多种配置,可以方便满足各种复杂的多任务处理场景。
- 面向对象接口
- 支持并发数配置
- 任务优先级调度
- 任务依赖关系
- 线程优先级配置
NSOperation简介
iOS并发编程中,把每个并发任务定义为一个Operation,对应的类名是NSOperation。NSOperation是一个抽象类,无法直接使用,它只定义了Operation的一些基本方法。我们需要创建一个继承于它的子类或者使用系统预定义的子类。目前系统预定义了两个子类:NSInvocationOperation和NSBlockOperation。
NSInvocationOperation
NSInvoationOperation是一个基于对象和selector的Operation,使用这个你只需要指定对象以及任务的selector,如果必要,你还可以设定传递的对象参数。
NSInvocationOperation *invacationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomethingWithObj:) object:obj];
同时当这个Operation完成后,你还可以获取Operation中Invation执行后返回的结果对象。
id result = [invacationOperation result];
NSBlockOperation
在一个Block中执行一个任务,这时我们就需要用到NSBlockOperation。可以通过blockOperationWithBlock:
方法来方便地创建一个NSBlockOperation:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
//Do something here.
}];
运行一个Operation
调用Operation的start
方法就可以直接运行一个Operation。
[operation start];
start
方法用来启动一个Operation任务。同时,Operation提供一个main
方法,你的所有任务都应该在main中进行处理。默认的start
方法中会先做出一些异常判断然后直接调用main
方法。如果需要自定义一个NSOperation必须重载main
方法来执行你所想要执行的任务。
@implementation CustomOperation
-(void)main {
@try {
// Do some work.
}
@catch(...) {
// Exception handle.
}
}
@end
取消一个Operation
要取消一个Operation,要向Operation对象发送cancel消息:
[operation cancel];
当向一个Operation对象发送cancel消息后,并不保证这个Operation对象一定能立刻取消,这取决于你的main
中对cancel
的处理。如果你在main
方法中没有对cancel
进行任何处理的话,发送cancel
消息是没有任何效果的。为了让Operation响应cancel
消息,那么你就要在main
方法中一些适当的地方手动的判断isCancelled
属性,如果返回YES
的话,应释放相关资源并立刻停止继续执行。
创建可并发的Operation
由于默认情况下Operation的start
方法中直接调用了main
方法,而main
方法中会有比较耗时的处理任务。如果我们在一段代码连续start
了多个Operation,这些Operation都是阻塞地依次执行完,因为第二个Operation必须等到第一个Operation执行完start
内的main
并返回。Operation默认都是不可并发的(使用了Operation Queue情况下除外,Operation Queue会独自管理自己的线程),因为默认情况下Operation并不额外创建线程。我们可以通过Operation的isConcurrent
方法来判断Operation是否是可并发的。如果要让Operation可并发,我们需要让main
在独立的线程中执行,并将isConcurrent
返回YES。
@implementation MyOperation{
BOOL executing;
BOOL finished;
}
- (BOOL)isConcurrent {
return YES;
}
- (void)start {
if ([self isCancelled])
{
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
@try {
// Do some work.
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
@catch(...) {
// Exception handle.
}
}
@end
当你自定义了start
或main
方法时,一定要手动的调用一些KVO通知方法,以便让对象的KVO机制可以正常运作。
设置Operation的completionBlock
每个Operation都可以设置一个completionBlock
,在Operation执行完成时自动执行这个Block。我们可以在此进行一些完成的处理。completionBlock
实现原理是对Operation的isFinnshed
字段进行KVO(Key-Value Observing),当监听到isFinnished
变成YES时,就执行completionBlock
。
operation.completionBlock = ^{
NSLog(@"finished");
};
设置Operation的线程优先级
我们可以为Operation设置一个线程优先级,即threadPriority
。那么执行main
的时候,线程优先级就会调整到所设置的线程优先级。这个默认值是0.5,我们可以在Operation执行前修改它。
operation.threadPriority = 0.1;
注意:如果你重载的start
方法,那么你需要自己来配置main
执行时的线程优先级和threadPriority
字段保持一致。
Operation状态变化
我们可以通过KVO机制来监听Operation的一下状态改变,比如一个Operation的执行状态或完成状态。这些状态的keypath包括以下几个:
- isCancelled
- isConcurrent
- isExecuting
- isFinished
- isReady
- dependencies
- queuePriority
- completionBlock
NSOperationQueue
NSOperationQueue是一个Operation执行队列,你可以将任何你想要执行的Operation添加到Operation Queue中,以在队列中执行。同时Operation和Operation Queue提供了很多可配置选项。Operation Queue的实现中,创建了一个或多个可管理的线程,为队列中的Operation提供可高度自定的执行环境。
Operation的依赖关系
有时候我们对任务的执行顺序有要求,一个任务必须在另一个任务执行之前完成,这就需要用到Operation的依赖(Dependency)属性。我们可以为每个Operation设定一些依赖的另外一些Operation,那么如果依赖的Operation没有全部执行完毕,这个Operation就不会被执行。
[operation addDependency:anotherOperation];
[operation removeDependency:anotherOperation];
如果将这些Operation和它所依赖的Operation加如队列中,那么Operation只有在它依赖的Operation都执行完毕后才可以被执行。这样我们就可以方便的控制Operation执行顺序。
Operation在队列中执行的优先级
Operation在队列中默认是按FIFO(First In First Out)顺序执行的。同时我们可以为单个的Operation设置一个执行的优先级,打乱这个顺序。当Queue有空闲资源执行新的Operation时,会优先执行当前队列中优先级最高的待执行Operation。
最大并发Operation数目
在一个Operation Queue中是可以同时执行多个Operation的,Operation Queue会动态的创建多个线程来完成相应Operation。具体的线程数是由Operation Queue来优化配置的,这一般取决与系统CPU的性能,比如CPU的核心数,和CPU的负载。但我们还是可以设置一个最大并发数的,那么Operation Queue就不会创建超过最大并发数量的线程。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
如果我们将maxConcurrentOperationCount
设置为1
,那么在队列中每次只能执行一个任务。这就是一个串行的执行队列了。
Simple Code
下面我写了一个简单的Simple Code来说明一下Operation和Operation Queue。
NSBlockOperation *operation5s = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation5s begin");
sleep(5);
NSLog(@"operation5s end");
}];
operation5s.queuePriority = NSOperationQueuePriorityHigh;
NSBlockOperation *operation1s = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation1s begin");
sleep(1);
NSLog(@"operation1s end");
}];
NSBlockOperation *operation2s = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation2s begin");
sleep(2);
NSLog(@"operation2s end");
}];
operation1s.completionBlock = ^{
NSLog(@"operation1s finished in completionBlock");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperation:operation1s];
[queue addOperation:operation2s];
[queue addOperation:operation5s];
[queue waitUntilAllOperationsAreFinished];
运行这段代码,我得到了一下输出结果:
operation1s begin
operation1s end
operation5s begin
operation1s finished in completionBlock
operation5s end
operation2s begin
operation2s end
为了更好的展示队列优先级效果,我把queue的maxConcurrentOperationCount
设置为1
,以便任务一个一个的执行。从上面日志可以看出,第一个operation1s执行完毕后,会执行operation5s,而不是operation2s,因为operation5s的queuePriority
是NSOperationQueuePriorityHigh
。而第一个线程总是会第一个执行。在看看2-4行,我们可以看出operation1s的completionBlock
比operation5s晚开始执行,说明它不在operation1s的线程中执行的。正如前面所说,completionBlock
是通过KVO监听执行,一般会运行在监听所在线程,而不是Operation执行的线程。
注意事项
- 当一个Operation被加入Queue中后,请不要对这个Operation再进行任何修改。因为一旦加入Queue,它随时就有可能会被执行,对它的任何修改都有可能导致它的运行状态不可控制。
threadPriority
仅仅影响了main
执行时的线程优先级,其他的方法包括completionBlock
都是以默认的优先级来执行的。如果自定义的话,也要注意在main
执行前设置好threadPriority
,执行完毕后要还原默认线程优先级。- 经测试,Operation的
threadPriority
字段只有在Operation单独执行时有效,在Operation Queue中是无效的。 - 第一个加入到Operation Queue中的Operation,无论它的优先级有多么低,总是会第一个执行。
【转】并发编程之Operation Queue的更多相关文章
- [转载]并发编程之Operation Queue和GCD
并发编程之Operation Queue http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html 随着移动设备的更新换代,移动设 ...
- iOS 并发编程之 Operation Queues
现如今移动设备也早已经进入了多核心 CPU 时代,并且随着时间的推移,CPU 的核心数只会增加不会减少.而作为软件开发者,我们需要做的就是尽可能地提高应用的并发性,来充分利用这些多核心 CPU 的性能 ...
- python并发编程之Queue线程、进程、协程通信(五)
单线程.多线程之间.进程之间.协程之间很多时候需要协同完成工作,这个时候它们需要进行通讯.或者说为了解耦,普遍采用Queue,生产消费模式. 系列文章 python并发编程之threading线程(一 ...
- 并发编程之 Condition 源码分析
前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...
- python并发编程之gevent协程(四)
协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...
- python并发编程之asyncio协程(三)
协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...
- python并发编程之multiprocessing进程(二)
python的multiprocessing模块是用来创建多进程的,下面对multiprocessing总结一下使用记录. 系列文章 python并发编程之threading线程(一) python并 ...
- python并发编程之threading线程(一)
进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程中拥有独立的内存单元,而多个线程共享内存等资源. 系列文章 py ...
- Java并发编程之CAS
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...
随机推荐
- IOSSelector的用法
1.首先,@selector 里面的方法不能传参数..不要相信网上的..都是复制粘贴的.2.分三步走:1.设置tag.2.设置btn的调用方法.3.使用参数2.看示例代码把.. UIButton ...
- lightOJ 1326 Race(第二类Stirling数)
题目链接:http://lightoj.com/volume_showproblem.php?problem=1326 题意:有n匹马赛跑.问有多少种不同的排名结果.可以有多匹马的排名相同. 思路:排 ...
- tengine lua 开源一 调用内部接口高效发送文件
tengine lua 开源一 调用内部接口高效发送文件 开源自己封装的sendfile 模块,可以高效的通过lua发送文件 源码地址:https://github.com/weinyzhou/Lu ...
- 国内顺利使用Google的另类技巧
在特殊的地方和特殊的时间,流畅顺利使用Google的方法也会变得很特殊.本文不定期保持维护更新,删除不能用的,增加新的网址. 分享一些奇葩的Google使用方法,通过下列网址也可以使用Google来搜 ...
- [转载]Python模块学习 ---- subprocess 创建子进程
[转自]http://blog.sciencenet.cn/blog-600900-499638.html 最近,我们老大要我写一个守护者程序,对服务器进程进行守护.如果服务器不幸挂掉了,守护者能即时 ...
- 函数fil_io
/********************************************************************//** Reads or writes data. This ...
- UVa 1401 (Tire树) Remember the Word
d(i)表示从i开始的后缀即S[i, L-1]的分解方法数,字符串为S[0, L-1] 则有d(i) = sum{ d(i+len(x)) | 单词x是S[i, L-1]的前缀 } 递推边界为d(L) ...
- UVa 540 Team Queue 【STL】
题意:给出t个团体,这t个团体排在一起,每次新来一个x排队,如果在整个的团体队列中,有x的队友,那么x排在它的队友的后面,如果他没有队友,则排在长队的队尾 求给出的每一个出队命令,输出出队的人的编号 ...
- 【转】iOS学习之Autolayout(代码添加约束) -- 不错不错
原文网址:http://www.cnblogs.com/HypeCheng/articles/4192154.html DECEMBER 07, 2013 学习资料 文章 Beginning Auto ...
- C# C/S 结构操作Ini系统文件
Winfrom 开发时,有时会将一些系统某个设置保存到Ini 类型的文件中.下面提供操作Ini 文件的代码: public static class IniFiles { [DllImport(&qu ...