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 секунды.
Dispatch_apply не работает, как я ожидал: почему заказ последовательный? Почему каждый цикл ожидает завершения предыдущего цикла?
Спасибо за помощь.
3 ответа
Поскольку ваше устройство имеет только один процессор, 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)
в конце кода просто есть, чтобы позволить потокам и блокам записывать свои сообщения до завершения программы. В зависимости от того, в какую программу вы вставляете код, он может вам не понадобиться.
Попробуйте посмотреть на этом сайте: 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
вместо?
Если кто-то хочет проверить, какой метод лучше всего подходит для решения проблемы 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");
}