Grand Central Dispatch

NOTE: This was written way back in 2013 when the GCD was still new, but finally decided to put it on my blog since I kept coming back to it in discussions lately. Have fun. If something is outdated, please tell me about it.

GCD…

  • … is short for Grand Central Dispatch
  • … is a core technology to handle asynchrony and concurrency intuitively
  • … is pretty damn efficient
  • … relies on blocks and dispatch queues
  • … is a C-level API, works with ARC
  • … does not absolve you of common sense! (Know your kung fu!)
  • … does not absolve you of deadlocks! (Know your runtime state aforehand!)
  • … can create strong reference cycles with blocks! (Know your lifetime!)

Blocks …

  • … are awesome and dead-simple
  • … are an intuitive way to pass functions
  • … are anonymous functions pointers
  • … get their references right (also in non-ARC)
  • … automatically capture variables from enclosing scope
  • … have strong references to non-scoped variables
  • … for GCD have no arguments and no return values: ^{ }

Queues …

  • … are essentially a primitive list of blocks
  • … are simple data structures: Enqueue blocks for execution
  • … come in two flavors: Serialized queues (one block at a time) or concurrent queues (concurrency with respect to other queues)
  • Concurrent Queues:
    • Execute multiple items in parallel
    • Dequeueing strongly FIFO, execution probably out of order
    • Most efficient parallel execution for system configuration

Target Queues

  • Dequeueing takes place in the target queues
  • Set a target queue to a serial queue that synchronizes with the respective queue
  • Target queues must be serial queues, behavior for concurrent target queues is undefined…
  • GCD supports arbitrarily deep hierachies

Dispatch barriers…

  • … are synchronization points
  • … will not run until all blocks submitted before are executed
  • Blocks submitted later will not run until barrier block has completed

Typical use case: Reader/Writer scenarios

  • Arbitrary number of multiple concurrent readers
  • Exclusive access for writers

How does it work?

Please find the code with examples below.

Retrieve well-known queues:

dispatch_get_main_queue();
dispatch_get_global_queue(prio, 0);
DISPATCH_QUEUE_PRIORITY_DEFAULT/_LOW/_HIGH/_BACKGROUND

Create queues:

dispatch_queue_create(label, attribute);
DISPATCH_QUEUE_SERIAL/_CONCURRENT

Schedule for execution:

dispatch_sync(queue, ^{ /*CODE*/ });
dispatch_async(queue, ^{ /*CODE*/ });
dispatch_after(delay, queue, ^{ /*CODE*/ });
dispatch_apply(iterations, queue, ^(size_t i){ /*CODE*/ };

Suspend and resume queues:

dispatch_suspend(queue);
dispatch_resume(queue);

Set target queues:

dispatch_set_target_queue(queue, target);

Insert dispatch barriers:

dispatch_barrier_async(queue, /*CODE*/);
dispatch_barrier_sync(queue, /*CODE*/);

Code Samples

main.m

#import "HelloGCD.h"


int main(int argc, const char * argv[])
{
    @autoreleasepool {
        __block BOOL finished = NO;
        HelloGCD *helloGCD = [HelloGCD new];
        
        if (NO)
        {
            [helloGCD introduction:^{ finished = YES; }];
        }
        else if (NO)
        {
            [helloGCD synchronousDispatchExample:^{ finished = YES; }];
        }
        else if (NO)
        {
            [helloGCD serialQueueExample:^{ finished = YES; }];
        }
        else if (NO)
        {
            [helloGCD concurrentQueueExample:^{ finished = YES; }];
        }
        else if (NO)
        {
            [helloGCD delayedExecutionExample:^{ finished = YES; }];
        }
        else if (NO)
        {
            [helloGCD multipleExecutionsExample:^{ finished = YES; }];
        }
        else if (NO)
        {
            [helloGCD multipleParallelExecutionsExample:^{ finished = YES; }];
        }
        else if (YES)
        {
            [helloGCD dispatchBarrierExample:^{ finished = YES; }];
        }
        else if (NO)
        {
            [helloGCD lowHighPriorityExample:^{ finished = YES; }];
        }
        else
        {
            finished = YES;
        }
        
        [HelloGCD processMainRunloopUntilTestPasses:^BOOL{ return finished; }];
    }
    return 0;
}

HelloGCD.h

@interface HelloGCD : NSObject

@property dispatch_queue_t serialQueue;
@property dispatch_queue_t concurrentQueue;
@property dispatch_queue_t lowPriorityQueue;
@property dispatch_queue_t highPriorityQueue;

+ (void)processMainRunloopUntilTestPasses:(BOOL (^)())test;

- (void)introduction:(void (^)())completionBlock;
- (void)synchronousDispatchExample:(void (^)())completionBlock;
- (void)serialQueueExample:(void (^)())completionBlock;
- (void)concurrentQueueExample:(void (^)())completionBlock;
- (void)delayedExecutionExample:(void (^)())completionBlock;
- (void)multipleExecutionsExample:(void (^)())completionBlock;
- (void)multipleParallelExecutionsExample:(void (^)())completionBlock;
- (void)dispatchBarrierExample:(void (^)())completionBlock;
- (void)lowHighPriorityExample:(void (^)())completionBlock;

@end

HelloGCD.m

#import "HelloGCD.h"

@implementation HelloGCD

+ (void)processMainRunloopUntilTestPasses:(BOOL (^)())test
{
    while (!test())
    {
        @autoreleasepool
        {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
        }
    }
}

- (id)init
{
    self = [super init];
    if (self)
    {
        // Basic GCD
        self.serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
        self.concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        
        // Advanced GCD
        self.lowPriorityQueue = dispatch_queue_create("lowPriorityQueue", DISPATCH_QUEUE_SERIAL);
        self.highPriorityQueue = dispatch_queue_create("highPriorityQueue", DISPATCH_QUEUE_SERIAL);
        dispatch_set_target_queue(self.lowPriorityQueue, self.highPriorityQueue);
    }
    return self;
}



// Blocks basics

- (void)introduction:(void (^)())completionBlock
{
    NSLog(@"===== Blocks basics =====");

    NSInteger i = 0;
    void (^block0)() = ^{ NSLog(@"block 0: i = %ld", i); };

    i = 1;
    void (^block1)() = ^{ NSLog(@"block 1: i = %ld", i); };

    block0();
    block1();
    completionBlock();
}


// Basic GCD

- (void)synchronousDispatchExample:(void (^)())completionBlock
{
    NSLog(@"===== Synchronous Dispatching Example =====");

    for (NSInteger i = 0; i < 5; i++)
    {
        dispatch_sync(self.serialQueue, ^{ NSLog(@"block %ld", i); });
    }

    dispatch_sync(self.serialQueue, completionBlock);
}

- (void)serialQueueExample:(void (^)())completionBlock
{
    NSLog(@"===== Serial Queue Asynchronous Dispatching Example =====");

    for (NSInteger i = 0; i < 5; i++)
    {
        dispatch_async(self.serialQueue, ^{ NSLog(@"block %ld", i); });
    }

    dispatch_async(self.serialQueue, completionBlock);
}

- (void)concurrentQueueExample:(void (^)())completionBlock
{
    NSLog(@"===== Concurrent Queue Asynchronous Dispatching Example =====");

    for (NSInteger i = 0; i < 5; i++)
    {
        dispatch_async(self.concurrentQueue, ^{ NSLog(@"block %ld", i); });
    }

    dispatch_barrier_async(self.concurrentQueue, completionBlock);
}



// Advanced GCD

- (void)delayedExecutionExample:(void (^)())completionBlock
{
    NSLog(@"===== Delayed Execution Example =====");

    int64_t delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, self.serialQueue, ^(void){
        NSLog(@"sorry i am late...");
        completionBlock();
    });
}

- (void)multipleExecutionsExample:(void (^)())completionBlock
{
    NSLog(@"===== Multiple Execution Example =====");
    dispatch_apply(5, self.serialQueue, ^(size_t i) { NSLog(@"scheduled %zd times", i); });
    completionBlock();
}

- (void)multipleParallelExecutionsExample:(void (^)())completionBlock
{
    NSLog(@"===== Multiple Execution Example =====");
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) { NSLog(@"scheduled %zd times", i); });
    completionBlock();
}

- (void)dispatchBarrierExample:(void (^)())completionBlock
{
    NSLog(@"===== Dispatch Barrier Example =====");

    for (NSInteger i = 0; i < 5; i++)
    {
        dispatch_async(self.concurrentQueue, ^{ NSLog(@"reader %ld", i); });
    }

    dispatch_barrier_async(self.concurrentQueue, ^{ NSLog(@"writer"); });

    for (NSInteger i = 5; i < 10; i++)
    {
        dispatch_async(self.concurrentQueue, ^{ NSLog(@"reader %ld", i); });
    }

    dispatch_barrier_async(self.concurrentQueue, completionBlock);
}


- (void)lowHighPriorityExample:(void (^)())completionBlock
{
    NSLog(@"===== Low/High Priority Example =====");

    for (NSInteger i = 0; i < 10; i++)
    {
        dispatch_async(self.lowPriorityQueue, ^{ NSLog(@"low priority block %ld", i); });
    }

    dispatch_suspend(self.lowPriorityQueue);
    dispatch_async(self.highPriorityQueue, ^{
        NSLog(@"high priority block");
        dispatch_resume(self.lowPriorityQueue);
    });

    dispatch_barrier_async(self.lowPriorityQueue, completionBlock);
}

@end