并发编程之Operation Queue

http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html

随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双核、甚至四核时代。如何充分发挥这些CPU的性能,会变得越来越重 要。在iOS中如果想要充分利用多核心CPU的优势,就要采用并发编程,提高CPU的利用率。iOS中并发编程中主要有2种方式Operation Queue和GCD(Grand Central Dispatch)。下面就来先来说一下Operation Queue。

异步调用和并发

在深入之前,首先说说异步调用和并发。这两个概念在并发编程中很容易弄混淆。异步调用是指调用时无需等待结果返回的调用,异步调用往往会触发后台线 程处理,比如NSURLConnection的异步网络回调。并发是指多个任务(线程)同时执行。在异步调用的实现中往往采用并发机制,然而并不是所有异 步都是并发机制,也有可能是其他机制,比如一些依靠中断进行的操作。

为什么Operation Queue

Operation Queue提供一个面向对象的并发编程接口,支持并发数,线程优先级,任务优先级,任务依赖关系等多种配置,可以方便满足各种复杂的多任务处理场景。

1.面向对象接口

2.支持并发数配置

3.任务优先级调度

4.任务依赖关系

5.线程优先级配置

NSOperation简介

iOS并发编程中,把每个并发任务定义为一个Operation,对应的类名是NSOperation。NSOperation是一个抽象类,无法 直接使用,它只定义了Operation的一些基本方法。我们需要创建一个继承于它的子类或者使用系统预定义的子类。目前系统预定义了两个子 类:NSInvocationOperation和NSBlockOperation。

NSInvocationOperation

NSInvoationOperation是一个基于对象和selector的Operation,使用这个你只需要指定对象以及任务的selector,如果必要,你还可以设定传递的对象参数。

  1. NSInvocationOperation *invacationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomethingWithObj:) object:obj];

同时当这个Operation完成后,你还可以获取Operation中Invation执行后返回的结果对象。

  1. id result = [invacationOperation result];

NSBlockOperation

在一个Block中执行一个任务,这时我们就需要用到NSBlockOperation。可以通过blockOperationWithBlock:方法来方便地创建一个NSBlockOperation:

  1. NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
  2. //Do something here.
  3. }];

运行一个Operation

调用Operation的start方法就可以直接运行一个Operation。

  1. [operation start];

start方法用来启动一个Operation任务。同时,Operation提供一个main方法,你的所有任务都应该在main中进行处理。默 认的start方法中会先做出一些异常判断然后直接调用main方法。如果需要自定义一个NSOperation必须重载main方法来执行你所想要执行 的任务。

  1. @implementation CustomOperation
  2. -(void)main {
  3. @try {
  4. // Do some work.
  5. }
  6. @catch(...) {
  7. // Exception handle.
  8. }
  9. }
  10. @end

取消一个Operation

要取消一个Operation,要向Operation对象发送cancel消息:

  1. [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。

  1. @implementation MyOperation{
  2. BOOL        executing;
  3. BOOL        finished;
  4. }
  5. - (BOOL)isConcurrent {
  6. return YES;
  7. }
  8. - (void)start {
  9. if ([self isCancelled])
  10. {
  11. [self willChangeValueForKey:@"isFinished"];
  12. finished = YES;
  13. [self didChangeValueForKey:@"isFinished"];
  14. return;
  15. }
  16. [self willChangeValueForKey:@"isExecuting"];
  17. [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
  18. executing = YES;
  19. [self didChangeValueForKey:@"isExecuting"];
  20. }
  21. - (void)main {
  22. @try {
  23. // Do some work.
  24. [self willChangeValueForKey:@"isFinished"];
  25. [self willChangeValueForKey:@"isExecuting"];
  26. executing = NO;
  27. finished = YES;
  28. [self didChangeValueForKey:@"isExecuting"];
  29. [self didChangeValueForKey:@"isFinished"];
  30. }
  31. @catch(...) {
  32. // Exception handle.
  33. }
  34. }
  35. @end

当你自定义了start或main方法时,一定要手动的调用一些KVO通知方法,以便让对象的KVO机制可以正常运作。

设置Operation的completionBlock

每个Operation都可以设置一个completionBlock,在Operation执行完成时自动执行这个Block。我们可以在此进行 一些完成的处理。completionBlock实现原理是对Operation的isFinnshed字段进行KVO(Key-Value Observing),当监听到isFinnished变成YES时,就执行completionBlock。

  1. operation.completionBlock = ^{
  2. NSLog(@"finished");
  3. };

设置Operation的线程优先级

我们可以为Operation设置一个线程优先级,即threadPriority。那么执行main的时候,线程优先级就会调整到所设置的线程优先级。这个默认值是0.5,我们可以在Operation执行前修改它。

  1. 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就不会被执行。

  1. [operation addDependency:anotherOperation];
  2. [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就不会创建超过最大并发数量的线程。

  1. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  2. queue.maxConcurrentOperationCount = 1;

如果我们将maxConcurrentOperationCount设置为1,那么在队列中每次只能执行一个任务。这就是一个串行的执行队列了。

Simple Code

下面我写了一个简单的Simple Code来说明一下Operation和Operation Queue。

  1. NSBlockOperation *operation5s = [NSBlockOperation blockOperationWithBlock:^{
  2. NSLog(@"operation5s begin");
  3. sleep(5);
  4. NSLog(@"operation5s end");
  5. }];
  6. operation5s.queuePriority = NSOperationQueuePriorityHigh;
  7. NSBlockOperation *operation1s = [NSBlockOperation blockOperationWithBlock:^{
  8. NSLog(@"operation1s begin");
  9. sleep(1);
  10. NSLog(@"operation1s end");
  11. }];
  12. NSBlockOperation *operation2s = [NSBlockOperation blockOperationWithBlock:^{
  13. NSLog(@"operation2s begin");
  14. sleep(2);
  15. NSLog(@"operation2s end");
  16. }];
  17. operation1s.completionBlock = ^{
  18. NSLog(@"operation1s finished in completionBlock");
  19. };
  20. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  21. queue.maxConcurrentOperationCount = 1;
  22. [queue addOperation:operation1s];
  23. [queue addOperation:operation2s];
  24. [queue addOperation:operation5s];
  25. [queue waitUntilAllOperationsAreFinished];

运行这段代码,我得到了一下输出结果:

  1. operation1s begin
  2. operation1s end
  3. operation5s begin
  4. operation1s finished in completionBlock
  5. operation5s end
  6. operation2s begin
  7. 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和GCD的更多相关文章

  1. 【转】并发编程之Operation Queue

    http://blog.xcodev.com/blog/2013/10/28/operation-queue-intro/ 随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双 ...

  2. iOS 并发编程之 Operation Queues

    现如今移动设备也早已经进入了多核心 CPU 时代,并且随着时间的推移,CPU 的核心数只会增加不会减少.而作为软件开发者,我们需要做的就是尽可能地提高应用的并发性,来充分利用这些多核心 CPU 的性能 ...

  3. python并发编程之Queue线程、进程、协程通信(五)

    单线程.多线程之间.进程之间.协程之间很多时候需要协同完成工作,这个时候它们需要进行通讯.或者说为了解耦,普遍采用Queue,生产消费模式. 系列文章 python并发编程之threading线程(一 ...

  4. Java并发编程之CAS

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...

  5. 并发编程之 Condition 源码分析

    前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...

  6. python并发编程之gevent协程(四)

    协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...

  7. python并发编程之asyncio协程(三)

    协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...

  8. python并发编程之multiprocessing进程(二)

    python的multiprocessing模块是用来创建多进程的,下面对multiprocessing总结一下使用记录. 系列文章 python并发编程之threading线程(一) python并 ...

  9. python并发编程之threading线程(一)

    进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程中拥有独立的内存单元,而多个线程共享内存等资源. 系列文章 py ...

随机推荐

  1. easyui treegrid逐步加载

    $("#bomStructureTable").treegrid({ url : "systemcontroller?id=10007",//首次查询路径 qu ...

  2. 关于《rsyslog+mysql+loganalyzer搭建日志服务器<个人笔记>》的反思

    关于<rsyslog+mysql+loganalyzer搭建日志服务器<个人笔记>>的反思--链接--http://www.cnblogs.com/drgcaosheng/p/ ...

  3. android中回调函数机制完全解析

    1.在要调用的业务操作中,创建一个接口,在接口中创建方法,这个方法表示的是我们原先要在业务类中执行的操作 public interface BackUpSmsListener { /** * 设置总进 ...

  4. SQL SERVER 将表中字符串转换为数字的函数 (详询请加qq:2085920154)

    在SQL SERVER 2005中,将表中字符串转换为数字的函数共2个:1. convert(int,字段名)   例如:select convert(int,'3')2. cast(字段名 as i ...

  5. Oracle 中的作业队列和队列调度

    一,启动执行作业的进程       在 Oracle 中,是使用 “作业队列协调进程(CJQ0)” 这个协调数据库实例的作业队列的后台进程,来监视作业队列中的作业表(JOB$),并启动作业队列进程(J ...

  6. DEV设计之自动流水号,DEV专家解答,自己折腾了半天也没有搞定,怪英文不好

    () 老外专家给了回答,结果没有全到懂,又折腾了20分钟朋友提示才搞定 获取一个自动增加1的流水号值, 第一个参数是本事的数据库连接对象,第2个参数是也这个值为唯一标识返回来一个增量的值,第三个好像没 ...

  7. Winform主窗体设计

    主窗体顶部为菜单按钮,子窗体内嵌入Panel显示 界面如下: 第二步,主窗体离不开的几个方法 1,点击菜单功能,加载子窗体 private void btnOpenForm_Click(object ...

  8. 123. Best Time to Buy and Sell Stock (三) leetcode解题笔记

    123. Best Time to Buy and Sell Stock III Say you have an array for which the ith element is the pric ...

  9. 微信公众账号开发之N个坑(二)

    上篇说到微信公众账号的几个坑,前面五个,已经说到菜单,宝宝继续往下赘述了.可惜,还不知道宝宝的宝宝到底是不是心疼宝宝呢,完了,我凌乱了... 回到正题,我们就不吐槽其他的了,上一篇说到微信的菜单了,那 ...

  10. angularjs 分页精华代码

    //pageinfo $scope.pageSize=10;$scope.currentType={{ current_type }};$scope.currentPage={{ json_encod ...