示例程式
寫一個簡單的程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
- (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方法
1 2 3 4 5 6 7 8 9 10 |
-(void)start{ while (true) { if(self.cancelled){ NSLog(@"已經取消"); return; } NSLog(@"start"); [NSThread sleepForTimeInterval:1]; } } |
實現的效果很簡單,列印三個strat,然後結束operation。
初探
根據閱讀GNU的原始碼,也只能是猜想,但是嘗試了很多方法,沒有找到可以驗證的方案,但是實現原理上還是有很多相似處的。
NSOperation有三種狀態,isReady, isExecuting, isFinished.
很多其他的引數也會隨著NSOperationQueue的addOperation操作而變化著。
例如:
1 |
[self.operationQueue addOperation:operation]; |
新增一個未完成的NSOperation,其實就是將NSOperation新增到一個動態陣列當中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
- (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操做情況還是很多的。
每一次屬性的變化,都伴隨著其他屬性的改變
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
- (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究竟是什麼?
整個原始碼都拿上來
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
- (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]中,感興趣可以去做一下測試。從原始碼中也是可以看出來的,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
- (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]。所以依然需要自己建立,例如:
1 |
[NSThread detachNewThreadSelector:@selector(start) toTarget:self withObject:nil]; |
現在來思考下,也就明白了為什麼NSOperationQueue要有兩種處理方式了,如果NSOperation支援併發,然後NSOperationQueue在為其分配執行緒,那就是執行緒裡面又跑了一條執行緒,這樣就很尷尬了,通過isConcurrent可以避免這種現象。
通常在大多數時候我們並不會直接去使用自定義的 NSOperation ,如果操作不復雜,可以直接使用 NSInvocationOperation 和 NSBlockOperation 這兩個子類。
如果真的需要使用多執行緒,通常都會用 NSOperationQueue來處理就可以了。
這裡也是僅僅簡單的探索了一下,上面的原始碼是封裝的NSThread,但是Apple的實現可能封裝的不是NSThread,因為斷點後並沒有看到跟NSThread相關的東西,還是有很多細節需要去推敲。
最後
我相信GNU的Foundation與Apple的Foundation的實現還是有一些相似處的,以上就是一些對應的猜想,但是有一些地方還沒有驗證
例如:原始碼中看出一個佇列裡對於非併發操作最多允許8個執行緒,但是我自己試了試,貌似不是這個樣子。可能是我的方法不對
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式