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相關的東西,還是有很多細節需要去推敲。
?
?
轉載于:https://www.cnblogs.com/fengmin/p/6108165.html
總結
以上是生活随笔為你收集整理的NSOperation, NSOperationQueue 原理探析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到别人找工作是什么意思
- 下一篇: mysql语句添加索引