• NSOperation, NSOperationQueue 原理探析


    通过GNUstep的Foundation来尝试探索下NSOperation,NSOperationQueue

    示例程序

    写一个简单的程序

    - (void)viewDidLoad {

        [super viewDidLoad];

        // Do any additional setup after loading the view, typically from a nib.

        

        [self configurationQueue];

        LDNSOperation *operation = [[LDNSOperation alloc] init];

        [self.operationQueue addOperation:operation];

        [NSThread sleepForTimeInterval:3];

        [operation cancel];

        

    }

    -(void)configurationQueue{

        self.operationQueue = [[NSOperationQueue alloc] init];

        self.operationQueue.maxConcurrentOperationCount = 4;

    }

    LDNSOperation为NSOperation的子类,重写strat方法

    -(void)start{

        while (true) {

            if(self.cancelled){

                NSLog(@"已经取消");

                return;

            }

            NSLog(@"start");

            [NSThread sleepForTimeInterval:1];

        }

    }

    实现的效果很简单,打印三个strat,然后结束operation。

    初探

    根据阅读GNU的源码,也只能是猜想,但是尝试了很多方法,没有找到可以验证的方案,但是实现原理上还是有很多相似处的。

    NSOperation有三种状态,isReady, isExecuting, isFinished.

    很多其他的参数也会随着NSOperationQueue的addOperation操作而变化着。

    例如:

    [self.operationQueue addOperation:operation];

    添加一个未完成的NSOperation,其实就是将NSOperation添加到一个动态数组当中

    - (void) addOperation: (NSOperation *)op

    {

      if (op == nil || NO == [op isKindOfClass: [NSOperation class]])

        {

          [NSException raise: NSInvalidArgumentException

      format: @"[%@-%@] object is not an NSOperation",

    NSStringFromClass([self class]), NSStringFromSelector(_cmd)];

        }

      [internal->lock lock];

      if (NSNotFound == [internal->operations indexOfObjectIdenticalTo: op]

        && NO == [op isFinished])

        {

          [op addObserver: self

       forKeyPath: @"isReady"

          options: NSKeyValueObservingOptionNew

          context: NULL];

          [self willChangeValueForKey: @"operations"];

          [self willChangeValueForKey: @"operationCount"];

          [internal->operations addObject: op];

          [self didChangeValueForKey: @"operationCount"];

          [self didChangeValueForKey: @"operations"];

          if (YES == [op isReady])

          {

              [self observeValueForKeyPath: @"isReady"

          ofObject: op

    change: nil

           context: nil];

          }

        }

      [internal->lock unlock];

    }

    internal就是一个内部类,指代的就是NSOperationQueue,这里也是一个KVO的手动通知,进行operations,与operationCount的改变通知。

    这里lock是NSRecursiveLock(递归锁),原因我猜测是因为递归锁的特性是可以被同一线程多次请求,而不会引起死锁。同一线程的多次addOperation操做情况还是很多的。

    每一次属性的变化,都伴随着其他属性的改变

    - (void) observeValueForKeyPath: (NSString *)keyPath

           ofObject: (id)object

                             change: (NSDictionary *)change

                            context: (void *)context

    {

      [internal->lock lock];

      if (YES == [object isFinished])

        {

          internal->executing--;

          [object removeObserver: self

      forKeyPath: @"isFinished"];

          [internal->lock unlock];

          [self willChangeValueForKey: @"operations"];

          [self willChangeValueForKey: @"operationCount"];

          [internal->lock lock];

          [internal->operations removeObjectIdenticalTo: object];

          [internal->lock unlock];

          [self didChangeValueForKey: @"operationCount"];

          [self didChangeValueForKey: @"operations"];

        }

      else if (YES == [object isReady])

        {

          [object removeObserver: self

      forKeyPath: @"isReady"];

          [internal->waiting addObject: object];

          [internal->lock unlock];

        }

      [self _execute];

    }

    其实在maxConcurrentOperationCount和suspended的setter方法里面都会调用_execute方法,以及在其他属性如operationCount、operations、值发生变化的时候都会调用它。

    那么_execute究竟是什么?

    整个源码都拿上来

    - (void) _execute

    {

      NSInteger max;

      [internal->lock lock];

      max = [self maxConcurrentOperationCount];

      if (NSOperationQueueDefaultMaxConcurrentOperationCount == max)

        {

          max = maxConcurrent;

        }

      while (NO == [self isSuspended]

        && max > internal->executing

        && [internal->waiting count] > 0)

        {

          NSOperation *op;

          op = [internal->waiting objectAtIndex: 0];

          [internal->waiting removeObjectAtIndex: 0];

          [op addObserver: self

       forKeyPath: @"isFinished"

          options: NSKeyValueObservingOptionNew

          context: NULL];

          internal->executing++;

          if (YES == [op isConcurrent])

    {

              [op start];

    }

          else

    {

      NSUInteger pending;

      [internal->cond lock];

      pending = [internal->starting count];

      [internal->starting addObject: op];

      if (0 == internal->threadCount

        || (pending > 0 && internal->threadCount

        {

          internal->threadCount++;

          [NSThread detachNewThreadSelector: @selector(_thread)

           toTarget: self

         withObject: nil];

        }

      /* Tell the thread pool that there is an operation to start.

       */

      [internal->cond unlockWithCondition: 1];

    }

        }

      [internal->lock unlock];

    }

    从源码中可以看到,根据isConcurrent分为直接执行,和非直接执行,isConcurrent为YES的话可以直接执行start操作,但是如果isConcurrent为NO,那么这里使用detachNewThreadSelector来创建新的线程去执行start。

    总结下来:

    • 所有的线程都很忙,并且没有达到threadCount的最大值的时候会创建新的线程,这代表queue并不是一个线程,也有可能有几个

    • _execute就是一个执行队列,依次将等待队列里面的所有operation进行start。

    其实对于start函数来说的话,一个NSOperation并没有新创建一条线程,依然操作在[NSThread currentThread]中,感兴趣可以去做一下测试。从源码中也是可以看出来的,

    - (void) start

    {

      NSAutoreleasePool *pool = [NSAutoreleasePool new];

      double prio = [NSThread  threadPriority];

      [internal->lock lock];

      NS_DURING

        {

          if (YES == [self isConcurrent])

    {

      [NSException raise: NSInvalidArgumentException

          format: @"[%@-%@] called on concurrent operation",

        NSStringFromClass([self class]), NSStringFromSelector(_cmd)];

    }

          if (YES == [self isExecuting])

    {

      [NSException raise: NSInvalidArgumentException

          format: @"[%@-%@] called on executing operation",

        NSStringFromClass([self class]), NSStringFromSelector(_cmd)];

    }

          if (YES == [self isFinished])

    {

      [NSException raise: NSInvalidArgumentException

          format: @"[%@-%@] called on finished operation",

        NSStringFromClass([self class]), NSStringFromSelector(_cmd)];

    }

          if (NO == [self isReady])

    {

      [NSException raise: NSInvalidArgumentException

          format: @"[%@-%@] called on operation which is not ready",

        NSStringFromClass([self class]), NSStringFromSelector(_cmd)];

    }

          if (NO == internal->executing)

    {

      [self willChangeValueForKey: @"isExecuting"];

      internal->executing = YES;

      [self didChangeValueForKey: @"isExecuting"];

    }

        }

      NS_HANDLER

        {

          [internal->lock unlock];

          [localException raise];

        }

      NS_ENDHANDLER

      [internal->lock unlock];

      NS_DURING

        {

          if (NO == [self isCancelled])

    {

      [NSThread setThreadPriority: internal->threadPriority];

      [self main];

    }

        }

      NS_HANDLER

        {

          [NSThread setThreadPriority:  prio];

          [localException raise];

        }

      NS_ENDHANDLER;

      [self _finish];

      [pool release];

    }

    总结

    整个过程伴随着很多属性的变化,同步这些属性,KVO在其中起着举足轻重的作用,通过源码也可以发现,NSOperationQueue对NSOperation的处理分为并发和非并发的情况。如果不想采用非并发的形式,我们可以直接自定义子类化,在NSOperationQueue中添加,并且管理就可以了,功能类似线程池的用法。

    但是如果想要自定义的NSOperation是并发的仅仅是重写isExecuting、isFinished、isConcurrent、isAsynchronous 这四个方法,isAsynchronous反回YES就可以了吗?

    从源码中我们可以看到,NSOperation的start依然使用的[NSThread currentThread]。所以依然需要自己创建,例如:

    [NSThread detachNewThreadSelector:@selector(start) toTarget:self withObject:nil];

    现在来思考下,也就明白了为什么NSOperationQueue要有两种处理方式了,如果NSOperation支持并发,然后NSOperationQueue在为其分配线程,那就是线程里面又跑了一条线程,这样就很尴尬了,通过isConcurrent可以避免这种现象。

    通常在大多数时候我们并不会直接去使用自定义的 NSOperation ,如果操作不复杂,可以直接使用 NSInvocationOperation 和 NSBlockOperation 这两个子类。

    如果真的需要使用多线程,通常都会用 NSOperationQueue来处理就可以了。

    这里也是仅仅简单的探索了一下,上面的源码是封装的NSThread,但是Apple的实现可能封装的不是NSThread,因为断点后并没有看到跟NSThread相关的东西,还是有很多细节需要去推敲。

     

  • 相关阅读:
    作业
    Java总结
    十四周总结
    十二周课程总结
    十一周总结
    自我介绍
    第二次
    第十二周作业
    第九周作业
    第十三周课程总结
  • 原文地址:https://www.cnblogs.com/fengmin/p/6108165.html
Copyright © 2020-2023  润新知