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