曲がった迷路その33 - Multi thread化の準備 [曲がった壁を持つ迷路の生成]
以前Cocoa環境でthreadを使うために勉強していたらMacOS X10.5からFoundation frameworkにNSOperation/NSOperationQueueというのが追加された。ちょこっとだけ研究してみたのをメ モったことがある。
こないだ作った曲がった壁を持つ迷路生成のアプリの時間のかかる反復計算の部分を別threadにしようとしてNSOperationを思い出そうとした。
そのメモに一部間違いがあったのがわかったのと、それとはべつによくわからんところが出てきたので書いておく。
NSOperationに関する以前の記述の間違い
NSOperationというのはNSThreadなんかで明示的にthreadを起こさなくても、簡単にmulti threadedなコードが書けるというもので、大まかには以前の記述があっている。ひとつ、僕の誤解があってNSOperationQueueのaddOperation:メソッドでqueueに追加して、waitUntilAllOperationsAreFinishedメソッドでqueueを走らせる、と思っていた。
が、addOperation:で追加した時点でNSOperation(のサブクラス)は実行が開始される。だから、waitUntilAllOperationsAreFinishedを呼ばなくてもどんどん追加していくだけでよかった。これはそのときのお勉強の間違い。お詫びして訂正します。
いずれにせよ、main threadですることがなくなって終わってしまうとその時点ですべての子threadも強制的に終わるので、main threadではwaitUntilAllOperationsAreFinishedを呼ぶなり子threadが全部終了したことを確かめるなりといったことをしなければいけない。
Multi-threadでのNSAutoreleasePool
NSThreadで明示的にthreadを起こしたとき、NSAutoreleasePoolはthreadごとに必要になる。従ってthreadをdetachして呼ばれたメソッドの先頭にNSAutoreleasePoolのインスタンスを作っておかないとリークする。例えばTestClassというのを作る。
@interface TestClass : NSObject { NSString *nameString; int maxc; } - (id)initWithName:(NSString *)name andCount:(int)c; - (void)main; @end以前、NSOperationの実験に使ったのと良く似ている、c秒間だけスリープして終わるmainというメソッドを持つクラス。main()関数と混同しないように別の名前にしたかったけどNSOperationのサブクラスにもしたいのでこのままにしておく。
こいつをFoundationToolのmain()関数で作って、別threadで呼んでやる。
int main(int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; TestClass *tcObj = [[TestClass alloc] initWithName:@"TestClass 1" andCount:2]; [NSThread detachNewThreadSelector:@selector(main) toTarget:tcObj withObject:nil]; [NSThread sleepForTimeInterval:3.0]; [pool drain]; return 0; }1行ずつ追えば、TestClassのインスタンスtcObjを作ってNSThreadを呼んでthreadをdetachしてそこでこのtcObjのmainというメソッドを呼ぶ、そのあと3秒待つ、ということをやっている。
ちなみに
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;というのはmacOS X10.5から使えるようになったメソッドで、それまでは
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];と書く必要があった。ちょっとだけ記述量が減った。こういうのは歓迎するけど、カテゴリにでも書けばすむ話ではある。
それはいいとして、これをXCodeの上で実行すると、デバッガコンソールに
2009-08-05 11:48:35.130 ThreadAndPool[30794:1103] *** _NSAutoreleaseNoPool(): Object 0x105d50 of class __NSCFDate autoreleased with no pool in place - just leaking Stack: (0x947f2d74 0x9471fdfc 0x2ab4 0x94725d70 0x9523a0c8)というログが書かれる。折り返って読みづらいけどようするにmainメソッドが呼ばれているのは新しいthreadの上で、そのmainメソッドの中にNSAutoreleasePoolのインスタンスがないので、そこでautoreleaseされたオブジェクトはリークするよ、と言っているのである。__NSCFDateはmainメソッドの中でタイムスタンプのログをとるために作っているNSDateのインスタンス。ちなみにThreadAndPoolというのはこの実行ファイルの名前。
ここでTestClassをNSThreadではなくてNSInvocationOperationで呼んでやる。
int main(int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; TestClass *tcObj = [[TestClass alloc] initWithName:@"TestClass 1" andCount:2]; NSString *str = [NSString stringWithString:@"autoreleased string"]; NSInvocationOperation *iope = [[[NSInvocationOperation alloc] initWithTarget:tcObj selector:@selector(main) object:nil] autorelease]; NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease]; [queue addOperation:iope]; [NSThread sleepForTimeInterval:3.0]; [pool drain]; return 0; }これも文字がごちゃごちゃしてるけど、NSInvocationOperationを作ってtcObjを登録してNSOperationQueueに投入している。こうすれば別threadでtcObjのmainメソッドが呼ばれるはず。
これを実行するとさっきとちがってNSAutoreleasePoolがない、というログは出ない。この動作はNSInvocationOperationを使わずに、TestClassをNSOperationのサブクラスにしてNSOperationQueueに投入しても同じ結果になる。
つまりNSOperationのmainメソッドの内部にNSAutoreleasePoolのインスタンスを作る必要はない、ということである。いくつかのサイトにNSOperationのexampleがあってたいていNSAutoreleasePoolを作ってるけど、これは必須ではない、ということ。Appleのドキュメントにはmainの中にNSAutoreleasePoolが必要と言う記述はないので、これはAppleとしては一貫してる。
では、NSOperationQueueか、あるいはNSOperationのstartメソッドで親切にもNSAtuoreleasePoolのインスタンスを作っているのか、というと実はそうではない。
これは次に続く...
2009-08-07 21:49
nice!(0)
コメント(0)
トラックバック(0)
コメント 0