web-dev-qa-db-ja.com

グランドセントラルディスパッチとNSThread

NSThreadとGrandCentral Dispatch(GCD)のテストコードをいくつか作成しました。

- (void)doIt:(NSNumber *)i
{
 sleep(1);
 NSLog(@"Thread#%i", [i intValue]);
}

- (IBAction)doWork:(id)sender
{

 for (int i = 0; 10 > i; i++) {
    NSNumber *t = [NSNumber numberWithInt:i];
    [NSThread detachNewThreadSelector:@selector(doIt:) toTarget:self withObject:t];
 }

 sleep(1);

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 dispatch_apply(10, queue, ^(size_t i) {
    sleep(1);
    NSLog(@"GCD#%u",(int)i);
 });
}

そして結果:

2011-04-13 19:41:07.806 GDC[1494:5e03] Thread#0
2011-04-13 19:41:07.813 GDC[1494:6903] Thread#3
2011-04-13 19:41:07.812 GDC[1494:6403] Thread#2
2011-04-13 19:41:07.812 GDC[1494:5f03] Thread#1
2011-04-13 19:41:07.813 GDC[1494:6e03] Thread#4
2011-04-13 19:41:07.814 GDC[1494:7303] Thread#5
2011-04-13 19:41:07.814 GDC[1494:7803] Thread#6
2011-04-13 19:41:07.815 GDC[1494:7d03] Thread#7
2011-04-13 19:41:07.815 GDC[1494:8203] Thread#8
2011-04-13 19:41:07.816 GDC[1494:8703] Thread#9
2011-04-13 19:41:08.812 GDC[1494:707] GCD#0
2011-04-13 19:41:09.816 GDC[1494:707] GCD#1
2011-04-13 19:41:10.819 GDC[1494:707] GCD#2
2011-04-13 19:41:11.825 GDC[1494:707] GCD#3
2011-04-13 19:41:12.828 GDC[1494:707] GCD#4
2011-04-13 19:41:13.833 GDC[1494:707] GCD#5
2011-04-13 19:41:14.838 GDC[1494:707] GCD#6
2011-04-13 19:41:15.848 GDC[1494:707] GCD#7
2011-04-13 19:41:16.853 GDC[1494:707] GCD#8
2011-04-13 19:41:17.857 GDC[1494:707] GCD#9

NSThreadsは期待どおりに機能します。タスクは同時に実行され、各スレッドは1秒間スリープします。

Dispatch_applyが期待どおりに機能しない:順序がシーケンシャルなのはなぜですか?各ループが前のループが終了するまで待機するのはなぜですか?

助けてくれてありがとう。

19
CarlJ

デバイスにはプロセッサが1つしかないため、GCDはおそらくブロックを実行するためのスレッドを1つだけ作成し、ブロックは順番に実行されます。ただし、10の異なるスレッドを作成しましたが、それぞれが利用可能な処理時間の一部を取得します。幸いなことに、スリープはプロセッサにそれほど負荷をかけないため、すべてのスレッドが一緒に実行されます。 4つまたは8つのプロセッシングコアを備えたマシンで同様のテストを試してみてください。GCDがより多くのブロックを並行して実行していることがわかります。

GCDの良いところは、必ずしもスレッドよりも優れたパフォーマンスを提供するということではありません。プログラマーは、スレッドの作成や、スレッドの数を使用可能なプロセッサーの数と一致させることを考える必要がないということです。プロセッサが利用可能になったときに実行される小さなタスクをたくさん作成し、システムにそれらのタスクをスケジュールさせることができます。

編集: Macの簡単なコマンドラインプログラムでコードを少し試してみました。以下のコメントで提案し、@ Ren-Dの回答でも述べたように、dispatch_async()ではなくdispatch_apply()を使用すると大きな違いが生じます。これが私が使用したコードです:

_- (void)doIt:(NSNumber *)i
{
    for (int j = 0; j < MAX_COUNT; j++)
        ;
    NSLog(@"Thread#%i", [i intValue]);
}

- (void)doWork:(id)sender
{
    for (int i = 0; i<10; i++) {
        NSNumber *t = [NSNumber numberWithInt:i];
        [NSThread detachNewThreadSelector:@selector(doIt:) toTarget:self withObject:t];
    }

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    for (size_t i = 0; i<10; i++) {
         dispatch_async(queue, ^(void) {
            for (int j = 0; j < MAX_COUNT; j++)
                ;
            NSLog(@"GCD#%u",(int)i);
        });
    }
    NSLog(@"Done.");
    sleep(15);
}
_

ご覧のとおり、sleep()呼び出しをforループに置き換えました。ループはカウントに時間がかかります。 (私はMacBook Proでコードを実行しました。iPhoneで実行している場合は、_MAX_COUNT_の値を下方修正することをお勧めします。)スレッドとの両方でsleep()を使用する場合ブロックの場合、dispatch_async()は、ブロックをスレッドのように動作させます。すべてのブロックは同時に実行され、ほぼ同時に完了します。カウントに切り替えると、動作が変わります。複数のスレッドはすべて同時に実行されますが、ブロックはグループで実行されます(私のマシンには2つのプロセッサコアがあるため、2つのグループでブロックを実行しました)。これはあなたが期待する通りです。 GCDの仕事は、できるだけ多くのタスクを同時に実行するのではなく、利用可能なリソースを最大限に活用して、タスクをキューに入れ、できるだけ早く終了することです。

上記のコードからの出力は次のとおりです。

_2011-04-14 02:48:46.840 BlockTest[14969:903] Hello, World!
2011-04-14 02:48:47.104 BlockTest[14969:903] Done.
2011-04-14 02:48:52.834 BlockTest[14969:1503] Thread#0
2011-04-14 02:48:52.941 BlockTest[14969:4f03] GCD#0
2011-04-14 02:48:52.952 BlockTest[14969:5003] GCD#1
2011-04-14 02:48:52.956 BlockTest[14969:4703] Thread#8
2011-04-14 02:48:53.030 BlockTest[14969:3703] Thread#4
2011-04-14 02:48:53.074 BlockTest[14969:2b03] Thread#1
2011-04-14 02:48:53.056 BlockTest[14969:4b03] Thread#9
2011-04-14 02:48:53.065 BlockTest[14969:3b03] Thread#5
2011-04-14 02:48:53.114 BlockTest[14969:3303] Thread#3
2011-04-14 02:48:53.138 BlockTest[14969:4303] Thread#7
2011-04-14 02:48:53.147 BlockTest[14969:3f03] Thread#6
2011-04-14 02:48:53.156 BlockTest[14969:2f03] Thread#2
2011-04-14 02:48:53.909 BlockTest[14969:4f03] GCD#2
2011-04-14 02:48:53.915 BlockTest[14969:5003] GCD#3
2011-04-14 02:48:54.700 BlockTest[14969:4f03] GCD#4
2011-04-14 02:48:54.721 BlockTest[14969:5003] GCD#5
2011-04-14 02:48:55.508 BlockTest[14969:4f03] GCD#6
2011-04-14 02:48:55.550 BlockTest[14969:5003] GCD#7
2011-04-14 02:48:56.321 BlockTest[14969:4f03] GCD#8
2011-04-14 02:48:56.345 BlockTest[14969:5003] GCD#9
_

ブロックのうちの2つは、1つを除くすべてのスレッドの前に実際に終了したことに注意してください。また、コードの最後にあるsleep(15)は、プログラムが終了する前にスレッドとブロックがメッセージをログに記録できるようにするためのものです。コードを貼り付けるプログラムの種類によっては、必要ない場合があります。

34
Caleb

このWebサイトを見てみてください: http://developer.Apple.com/library/ios/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html

IOS環境では、ターゲットキューがdispatch_applyによって返される並行キューである場合、dispatch_get_global_queueは渡されるキューに依存すると言われています(これはあなたの場合です) )、ブロックを同時に呼び出すことができます。

したがって、IS動作しているので、非同期で実行されているかのように実行されます。また、コードが実行されているデバイスが結果に影響を与える可能性があります(前述のように) @Calebによる)しかし、私の提案は、代わりにdispatch_asyncを試してみることです。

7
Rpranata

誰かがテストしたい場合、どの方法がスピード化の問題に最適であるか、ここにコードがあります:

#define MAX_COUNT 99999999
#define HOW_MUCH 10
- (void)doIt:(NSNumber *)i
{
    for (int j = 0; j < MAX_COUNT; j++)
        ;
    NSLog(@"Thread#%i", [i intValue]);
}


- (IBAction)doWork:(id)sender
{
    NSLog(@"START");

    for (int i = 0; i < HOW_MUCH; i++) {
        NSNumber *t = [NSNumber numberWithInt:i];
        [NSThread detachNewThreadSelector:@selector(doIt:) toTarget:self withObject:t];
    }

    sleep(3);


    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(HOW_MUCH, queue, ^(size_t i) {
        for (int j = 0; j < MAX_COUNT; j++)
            ;
        NSLog(@"GCD APPLY %u",(int)i);
    });


    sleep(3);

    for (size_t k = 0; k < HOW_MUCH; k++) {
        dispatch_async(queue, ^(void) {
            for (int j = 0; j < MAX_COUNT; j++)
                ;
            NSLog(@"GCD ASYNC#%u",(int)k);
        });
    }

    sleep(10);
    NSLog(@"DONE");
}
2
CarlJ