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相关的东西,还是有很多细节需要去推敲。
NSOperation, NSOperationQueue 原理探析的更多相关文章
- mod_php模式原理探析
1.PHP与Apache工作模式 在传统的LAMP架构中,PHP与Apache交互时,至少有两种方式『运行PHP』: 使用CGI:Apache发送请求至php-cgi进程,php-cgi进程调用PHP ...
- WebRTC的RTCPeerConnection()原理探析
从getUserMedia()到RTCPeerConnection(),自认为难度陡增.我想一方面是之前在Linux平台上学习ROS调用摄像头时,对底层的外设接口调用.摄像头参数都有学习理解:另一方面 ...
- 深入探析koa之中间件流程控制篇
koa被认为是第二代web后端开发框架,相比于前代express而言,其最大的特色无疑就是解决了回调金字塔的问题,让异步的写法更加的简洁.在使用koa的过程中,其实一直比较好奇koa内部的实现机理.最 ...
- 开源中文分词工具探析(三):Ansj
Ansj是由孙健(ansjsun)开源的一个中文分词器,为ICTLAS的Java版本,也采用了Bigram + HMM分词模型(可参考我之前写的文章):在Bigram分词的基础上,识别未登录词,以提高 ...
- 开源中文分词工具探析(五):FNLP
FNLP是由Fudan NLP实验室的邱锡鹏老师开源的一套Java写就的中文NLP工具包,提供诸如分词.词性标注.文本分类.依存句法分析等功能. [开源中文分词工具探析]系列: 中文分词工具探析(一) ...
- ELF格式探析之三:sections
前文链接: ELF格式探析之一:Segment和Section ELF格式探析之二:文件头ELF Header详解 今天我们讲对目标文件(可重定位文件)和可执行文件都很重要的section. 我们在讲 ...
- 浏览器环境下Javascript脚本加载与执行探析之DOMContentLoaded
在”浏览器环境下Javascript脚本加载与执行探析“系列文章的前几篇,分别针对浏览器环境下JavaScript加载与执行相关的知识点或者属性进行了探究,感兴趣的同学可以先行阅读前几篇文章,了解相关 ...
- 深入探析 Rational AppScan Standard Edition 多步骤操作
序言 IBM Rational AppScan Standard(下文简称 AppScan)作为面向 Web 应用安全黑盒检测的自动化工具,得到业界的广泛认可和应用.很多人使用 AppScan 时都采 ...
- Spring IOC 容器预启动流程源码探析
Spring IOC 容器预启动流程源码探析 在应用程序中,一般是通过创建ClassPathXmlApplicationContext或AnnotationConfigApplicationConte ...
随机推荐
- SQL SERVER CXPACKET-Parallelism Wait Type 的惯用解决方案
最近我的两个库出现,出现较多的CXPACKET等待,在网上找了一下资料.其中有篇一个SQL Server专栏作家的文章不错,也解决了我的一些疑问,就翻译在这里. 翻译整理仅用于传播资讯之目的. 原文出 ...
- Android studio安装配置常见问题及其解决方案
Android studio 是目前android公司主推的一款开发工具,相比较以前eclipse,它自己内部就集成了SDK等,方便开发.这几天我也尝试从官网下载了一个android studio进行 ...
- 深度学习哪家强?吴恩达、Udacity和Fast.ai的课程我们替你分析好了
http://www.jianshu.com/p/28f5473c66a3 翻译 | AI科技大本营(rgznai100) 参与 | reason_W 引言 过去2年,我一直积极专注于深度学习领域.我 ...
- 下周二推出“音视频技术WebRTC初探”公开课,欢迎捧场!
下周二推出"音视频技术WebRTC初探"公开课,欢迎捧场! 公开课课程链接:http://edu.csdn.net/huiyiCourse/detail/90 课程的解说资料 ...
- Python操作记录
1.写入中文出错,需要执行 reload(sys) sys.setdefaultencoding('utf8') 2.json.dump中文写入为\xxxx ensure_ascii=False
- mongoDB开发规范
mongoDB库的设计 mongodb数据库命名规范:db_xxxx 禁止使用任何 " _ "(下划线)外的特殊字符 禁止使用数字打头的库名 数据库名最多为 64字符 mongoD ...
- 其原因可能是堆被损坏,这也说明 xxx.exe 中或它所加载的任何 DLL 中有 bug
1.代码如下: string src ="abcdabcd"; char* dst = new char[8]; strcpy(dst,src.c_str()); delete[] ...
- 查看oracle的sql语句历史记录和锁表的情况
查看oracle的sql语句历史记录和锁表的情况 (2012-01-04 20:59:59) 转载▼ 标签: 杂谈 分类: database 查询sql的历史记录 select * from v$sq ...
- 高级javaScript程序形成过程(进阶)
实现过程 1.创建对象 if (window.ke){return;} 2.创建私有变量 var _TIME = new Date().getTime(); 3.创建私有函数 _fn(){} 4.创建 ...
- SQL语句 怎么把从一个表中查出来数据插入到另一个表中
(1).select * into destTbl from srcTbl(2).insert into destTbl(fld1, fld2) select fld1, 5 from srcTbl