Entries

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

NSOperationQueueとスレッドの関係

タグ: Objective-C Mac NSOperation

NSOperationQueueを使ったプログラムをデバッグしていてふと気付いたのだが、NSOperationQueueは何もクソ真面目に「concurrentではない全てのoperationのために毎回スレッドを作っている」わけではないらしい。

例えば以下のコード。

@interface Operation : NSOperation
{
    int number;
}
@property (nonatomic) int number;
@end

@implementation Operation
@synthesize number;
- (void)main
{
    [NSThread sleepForTimeInterval:1];
    NSLog(@"%@ %d", [[NSThread currentThread] retain], number);
    // 同じアドレスに別のインスタンスが作られると紛らわしいのでわざとリークさせている
}
@end

#define NUM 10

void test()
{
    NSOperationQueue *queues[NUM];
    NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
    for(int i = 0; i < NUM; i++)
    {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue setMaxConcurrentOperationCount:1];
        Operation *op = [[Operation alloc] init];
        op.number = i;
        [queue addOperation:op];
        [op release];
        queues[i] = queue;
    }
    for(int i = 0; i < NUM; i++)
    {
        [queues[i] waitUntilAllOperationsAreFinished];
        [queues[i] release];
    }
    NSTimeInterval end = [NSDate timeIntervalSinceReferenceDate];
    NSLog(@"%f", end - start);
}

「『1秒待機』するoperationを10個のNSOperationQueueインスタンスに1つずつaddする」このコードをMacBook Pro(C2D、Mac OS X 10.5.6)で実行すると次のような結果となる。

Test[383:1103] <NSThread: 0x111080>{name = (null), num = 3} 1
Test[383:1003] <NSThread: 0x1107e0>{name = (null), num = 2} 0
Test[383:1703] <NSThread: 0x104f80>{name = (null), num = 4} 2
Test[383:1803] <NSThread: 0x111220>{name = (null), num = 5} 3
Test[383:1903] <NSThread: 0x110f40>{name = (null), num = 6} 4
Test[383:1a03] <NSThread: 0x110e10>{name = (null), num = 7} 5
Test[383:1c03] <NSThread: 0x111300>{name = (null), num = 9} 7
Test[383:1b03] <NSThread: 0x111490>{name = (null), num = 8} 6
Test[383:1d03] <NSThread: 0x1112c0>{name = (null), num = 10} 8
Test[383:1e03] <NSThread: 0x110fe0>{name = (null), num = 11} 9
Test[383:813] 1.166345

全処理完了に約1秒。
全てのoperationが並行して動作したようだ。

ではOperationクラスのmainを次のように書き換えたらどうだろう。

- (void)main
{
    NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
    while([NSDate timeIntervalSinceReferenceDate] - start < 1)
    {}
    NSLog(@"%@ %d", [[NSThread currentThread] retain], number);
    // 同じアドレスに別のインスタンスが作られると紛らわしいのでわざとリークさせる
}
Test[441:1003] <NSThread: 0x10fc00>{name = (null), num = 2} 0
Test[441:1103] <NSThread: 0x104f80>{name = (null), num = 3} 1
Test[441:1003] <NSThread: 0x10f8f0>{name = (null), num = 4} 2
Test[441:1103] <NSThread: 0x10e370>{name = (null), num = 5} 3
Test[441:190b] <NSThread: 0x10e230>{name = (null), num = 6} 4
Test[441:1c07] <NSThread: 0x1107c0>{name = (null), num = 7} 5
Test[441:1003] <NSThread: 0x110580>{name = (null), num = 8} 6
Test[441:1103] <NSThread: 0x10eb70>{name = (null), num = 9} 7
Test[441:190b] <NSThread: 0x10ee70>{name = (null), num = 10} 8
Test[441:1c07] <NSThread: 0x110680>{name = (null), num = 11} 9
Concurrent[441:813] 5.010538

全処理完了に約5秒。
前のコードもこのコードもoperationの内容は「1秒待機」に変わりないが、こちらはループで無駄にCPU負荷が掛かっているため、同時に処理されるoprationの数がNSOperationQueueクラス単位?(少なくともインスタンス単位ではない)で2(CPUコア数?)に制限されたようだ。

さて、ここで気になるのは「最終的にいくつのスレッドが生成されたか」ということ。
結果を見ると、どちらのログでも「全てのoperationに異なるNSThreadが割り振られている」ので、メインスレッドを除いて「10スレッド生成された」と言えそうだが、これはある意味では正しくない。
というのも、NSThreadの手前に出力されている

Test[441:1003]

これは「プロセス名:[プロセスID:スレッドID]」なわけだが、このスレッドIDが最初の結果ではメインスレッドの分(831)を除いて「10種類」あるのに対し、次の結果では「4種類」しかないからだ。
つまり、後者は「4スレッドで処理を回している」ことになる。
もちろん、デタッチされたスレッドIDは再利用可能なため、「スレッドIDが4つだから生成されたスレッド数が4」と言い切れるわけではない。
しかし、デバッガを使って関数testの終わりで

(gdb) t a a bt

してみると、

Thread 5 (process 441 thread 0x1c07):
#0  0x94ff1292 in __workq_ops ()
#1  0x94ff298a in workqueue_exit ()
#2  0x94ff12c2 in start_wqthread ()
Current language:  auto; currently objective-c

Thread 4 (process 441 thread 0x190b):
#0  0x94ff1292 in __workq_ops ()
#1  0x94ff298a in workqueue_exit ()
#2  0x94ff12c2 in start_wqthread ()

Thread 3 (process 441 thread 0x1103):
#0  0x94ff1292 in __workq_ops ()
#1  0x94ff298a in workqueue_exit ()
#2  0x94ff12c2 in start_wqthread ()

Thread 2 (process 441 thread 0x1003):
#0  0x94ff1292 in __workq_ops ()
#1  0x94ff298a in workqueue_exit ()
#2  0x94ff12c2 in start_wqthread ()

Thread 1 (process 441 local thread 0x2e03):

このように「処理を回したスレッドが次の処理を求めて待機しているような」風景が見られるので、「処理を終えたスレッドのIDが再利用された」と考えるよりも、「最小限のスレッド数で処理を回している」と考えた方がやはりスマートだろう。

ちなみに生成されたスレッドの数が「2」ではなく「4」なのは、おそらく「NSLogでもたついている間に新しいスレッドを作って次のoperationが開始された」ため。
mainの中のNSLogを削除すると、少なくとも私の環境では最終的に「2」スレッドしか残らない。

スポンサーサイト

コメント

コメントの投稿

コメントの投稿
管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://idlysphere.blog66.fc2.com/tb.php/197-366814c8
この記事にトラックバックする(FC2ブログユーザー)

Appendix

タグ

Blog内検索

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。