Grand Central Dispatch vs. NSThread

CarlJ picture CarlJ · Apr 13, 2011 · Viewed 20.5k times · Source

I created some test code for NSThread and 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);
 });
}

And the results:

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 work as I expected: The tasks run concurrently and each thread sleeps for 1 second.

The dispatch_apply does not work as I expected: Why is the order sequential? Why does each loop wait until the previous loop finishes?

Thanks for the help.

Answer

Caleb picture Caleb · Apr 13, 2011

Because your device only has one processor, GCD probably only creates one thread for executing blocks and your blocks execute sequentially. You've created 10 different threads, though, and those each get a little piece of the available processing time. Fortunately, sleeping isn't very processor-intensive, so all your threads run together pretty well. Try a similar test on a machine with 4 or 8 processing cores, and you'll see GCD run more of your blocks in parallel.

The nice thing about GCD isn't that it necessarily offers better performance than threads, it's that the programmer doesn't have to think about creating threads or matching the number of threads to the number of available processors. You can create lots of little tasks that will execute as a processor becomes available and let the system schedule those tasks for you.

Edit: I played around with your code a bit in a simple command-line program on my Mac. As I suggested in my comment below, and also mentioned in @Ren-D's answer, using dispatch_async() rather than dispatch_apply() makes a big difference. Here's the code I used:

- (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);
}

As you can see, I replaced your sleep() calls with for loops that spend some time counting. (I ran the code on a MacBook Pro -- you might want to adjust the value of MAX_COUNT downward if you're running on an iPhone.) If you use sleep() in both the threads and the block, then dispatch_async() makes the blocks behave just like the threads -- all blocks run concurrently and complete at about the same time. Switching to counting changes that behavior -- multiple threads all run concurrently, but the blocks execute in groups (my machine has two processor cores, so it ran the blocks in groups of two). This is exactly as you'd expect; GCD's job is to queue tasks and finish them as quickly as possible making the best use of available resources, not to run as many tasks concurrently as possible.

Here's output from the code above:

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

Note that two of the blocks actually finished before all but one of the threads. Also: the sleep(15) at the end of the code is just there to let the threads and blocks log their messages before the program terminates. Depending on what sort of program you paste the code into, you may not need it.