通过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相关的东西,还是有很多细节需要去推敲。

 

NSOperation, NSOperationQueue 原理探析的更多相关文章

  1. mod_php模式原理探析

    1.PHP与Apache工作模式 在传统的LAMP架构中,PHP与Apache交互时,至少有两种方式『运行PHP』: 使用CGI:Apache发送请求至php-cgi进程,php-cgi进程调用PHP ...

  2. WebRTC的RTCPeerConnection()原理探析

    从getUserMedia()到RTCPeerConnection(),自认为难度陡增.我想一方面是之前在Linux平台上学习ROS调用摄像头时,对底层的外设接口调用.摄像头参数都有学习理解:另一方面 ...

  3. 深入探析koa之中间件流程控制篇

    koa被认为是第二代web后端开发框架,相比于前代express而言,其最大的特色无疑就是解决了回调金字塔的问题,让异步的写法更加的简洁.在使用koa的过程中,其实一直比较好奇koa内部的实现机理.最 ...

  4. 开源中文分词工具探析(三):Ansj

    Ansj是由孙健(ansjsun)开源的一个中文分词器,为ICTLAS的Java版本,也采用了Bigram + HMM分词模型(可参考我之前写的文章):在Bigram分词的基础上,识别未登录词,以提高 ...

  5. 开源中文分词工具探析(五):FNLP

    FNLP是由Fudan NLP实验室的邱锡鹏老师开源的一套Java写就的中文NLP工具包,提供诸如分词.词性标注.文本分类.依存句法分析等功能. [开源中文分词工具探析]系列: 中文分词工具探析(一) ...

  6. ELF格式探析之三:sections

    前文链接: ELF格式探析之一:Segment和Section ELF格式探析之二:文件头ELF Header详解 今天我们讲对目标文件(可重定位文件)和可执行文件都很重要的section. 我们在讲 ...

  7. 浏览器环境下Javascript脚本加载与执行探析之DOMContentLoaded

    在”浏览器环境下Javascript脚本加载与执行探析“系列文章的前几篇,分别针对浏览器环境下JavaScript加载与执行相关的知识点或者属性进行了探究,感兴趣的同学可以先行阅读前几篇文章,了解相关 ...

  8. 深入探析 Rational AppScan Standard Edition 多步骤操作

    序言 IBM Rational AppScan Standard(下文简称 AppScan)作为面向 Web 应用安全黑盒检测的自动化工具,得到业界的广泛认可和应用.很多人使用 AppScan 时都采 ...

  9. Spring IOC 容器预启动流程源码探析

    Spring IOC 容器预启动流程源码探析 在应用程序中,一般是通过创建ClassPathXmlApplicationContext或AnnotationConfigApplicationConte ...

随机推荐

  1. cdn网络加速

    CDN全称Content Delivery Network,即内容分发网络.其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快.更稳定.通过在网络各处放置节点服 ...

  2. 如何去除图片上的文字(PS使用教程)

    很多时候由于工作的需要,需要对我们的图片进行修改,修改的同时还想要保存我们的图片背景,所以很多人就不知道怎么弄了,小编跟大家分享一下使用PS如何简单的去掉图片上的文字,希望对大家有所帮助! 方法/步骤 ...

  3. Spark(十二)--性能调优篇

    一段程序只能完成功能是没有用的,只能能够稳定.高效率地运行才是生成环境所需要的. 本篇记录了Spark各个角度的调优技巧,以备不时之需. 一.配置参数的方式和观察性能的方式 额...从最基本的开始讲, ...

  4. Pandas对行情数据的预处理

    库里是过去抓取的行情数据,间隔6秒,每分钟8-10个数据不等,还有开盘前后的一些数据,用Pandas可以更加优雅地进行处理. 需要把当前时间设置为index df=df.set_index('time ...

  5. [Angular] Communicate with Angular Elements using Inputs and Events

    In a real world scenario we obviously need to be able to communicate with an Angular Element embedde ...

  6. Struts2添加了<s:debug>后页面无效果的解决方案

    一.环境 Struts2版本 struts2.5 二.问题 在jsp页面中添加了<s:debug>后页面上无任何展示. 三.解决 在struts.xml中的struts节点下添加如下常量即 ...

  7. 浅谈关于QT中Webkit内核浏览器

    关于QT中Webkit内核浏览器是本文要介绍的内容,主要是来学习QT中webkit中浏览器的使用.提起WebKit,大家自然而然地想到浏览器.作为浏览器内部的主要构件,WebKit的主要工作是渲染.给 ...

  8. 什么是Session分布式共享

    在了解session分布式共享之前先来了解Session.Redis和Nginx的相关知识. 一.Session相关知识 1.Session 介绍 Session在网络应用中,称为“会话控制”. 每个 ...

  9. javascript string replace 正则替换

    利用正则式实现首字母大写,丧心病狂是不是?好好的substr不用. JavaScript replace() 方法 r = /^(.)(?=.*)/; str = 'abc'; var str2 = ...

  10. flume监控

    Flume本身提供了http, ganglia的监控服务,而我们目前主要使用zabbix做监控.因此,我们为Flume添加了zabbix监控模块,和sa的监控服务无缝融合. 另一方面,净化Flume的 ...