Grand Central Dispatch против NSThread

Я создал некоторый тестовый код для NSThread и Grand Central 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 секунду.

Диспетчер_пакеты не работает так, как я ожидал: почему порядок последователен? Почему каждый цикл ожидает окончания предыдущего цикла?

Спасибо за помощь.

Ответ 1

Поскольку ваше устройство имеет только один процессор, GCD, вероятно, создает только один поток для выполнения блоков, а ваши блоки выполняются последовательно. Тем не менее, вы создали 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 - вы можете настроить значение MAX_COUNT вниз, если вы работаете на iPhone.) Если вы используете sleep() как в потоках, так и в блоке, тогда dispatch_async() делает блоки ведут себя точно так же, как потоки - все блоки работают одновременно и полностью примерно в одно и то же время. Переключение на счетные изменения, поведение которых - одновременное выполнение нескольких потоков, но блоки выполняются группами (моя машина имеет два процессорных ядра, поэтому она управляла блоками по группам из двух). Это точно так, как вы ожидали; Задача 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

Обратите внимание, что два из блоков фактически закончены перед всеми, кроме одного из потоков. Также: sleep (15) в конце кода находится там, чтобы потоки и блоки регистрировали свои сообщения до завершения программы. В зависимости от того, какую программу вы вставляете в код, вам может и не понадобиться.

Ответ 2

Попробуйте посмотреть этот сайт: http://developer.apple.com/library/ios/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html

В среде IOS сказано, что dispatch_apply будет зависеть от переданной очереди, если целевая очередь является параллельной очередью, возвращаемой dispatch_get_global_queue (это ваш случай), блок может быть вызван одновременно.

Итак, я думаю, что он работает, просто так происходит, что он запускается, как если бы он выполнялся асинхронно. Кроме того, какое устройство, на котором выполняется код, может сыграть роль в результате (как упоминалось в @Caleb). Но мое предложение, может быть, попробуйте dispatch_async вместо этого?

Ответ 3

Если кто-то хочет проверить, какой метод лучше всего подходит для проблемы spedify, вот код:

#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");
}