Grand Central Dispatch
Design Patterns
By Robert Brown
@robby_brown
Blocks
Blocks

Blocks are a proposed addition to C.
Like a function pointer, except it also stores the context
the block was created in.
Similar to closures, lambdas, and anonymous functions
in other languages.
Blocks
Declaration Syntax:
returnType (^blockName) (Arguments)
Blocks may be anonymous.
Definition Syntax:
^ returnType (Arguments) { code; }
The return type and arguments are optional.
GCD provides function pointer variants to the block APIs.
Blocks
Blocks can modify local variables outside of their scope
if the variables have the new __block keyword.
Global and static variables can be modified without the
__block keyword.
Blocks automatically retain Objective-C objects, except
objects that use the __block modifier.
C objects must be manually retained.
Beware of retain cycles.
Grand Central Dispatch
What is GCD?

GCD is a lightweight multithreading engine.
Uses a thread pool.
Developers create queues of blocks rather than
threads.
Uses lock-less exclusion rather than mutual exclusion.
Replaces blocking and polling APIs.
Why Multithread on a Single
Core?

Keeps the UI responsive.
UI code runs on the main thread.
Everything else runs on a background thread.
Prevents the main thread from blocking or waiting.
Frequently Used APIs
dispatch_async(queue, block);
dispatch_once(token, block);
dispatch_queue_create(name, type);
dispatch_set_target_queue(object, queue);
dispatch_get_global_queue(priority, flags);
dispatch_release(object);
Global Queues

Four global queues:
  Main, Low, Default, and High.
Only the main thread services the main queue.
The three other queues determine the priority of
background tasks.
Enqueuing is thread safe.
Design Patterns
What is a Design Pattern?


After working on related problems, patterns often
appear in the solutions.
Formalized description of best practice.
Initialization Pattern
dispatch_once

Guaranteed to run only once for the lifetime of the
application.
Fast and thread safe.
Very easy to use.
Great for singletons and static class variables.
The Old Way
static MyObject * myObject = nil;
+ myObject {
    @synchronized(self) {
        if (!myObject) myObject = [MyObject new];
    }
    return myObject;
}
Problems With
@synchronized
@synchronized is slow.
When synchronizing on the class instance, all
other methods that synchronize on it will
temporarily block incoming messages.
You can’t synchronize on the class variable since it
is initially nil.
Using a custom lock also faces the initialization
problem.
The GCD Way
static MyObject * myObject = nil;
+ myObject {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
          if (!myObject) myObject = [MyObject new];
    });
    return myObject;
}
Lockless Exclusion Pattern
Mutual Exclusion

Mutual exclusion is classically handled by semaphores
or locks.
Both are most efficient when there is no sharing
conflicts, (i.e. when you don’t need them) and don’t
scale well.
Queues are efficient and scale very well.
In fact, queues are most efficient under high conflict.
Thread-safe Access
// myQueue must be a serial queue      NSLock * lock = [NSLock new];



dispatch_async(myQueue, ^{             [lock lock];

      [self setMySharedVariable:42];   [self setMySharedVariable:42];

});                                    [lock unlock];
Enforcing Lockless
Exclusion

- (NSInteger)mySharedVariable {
     NSAssert(dispatch_get_current_queue() ==
     myQueue, @“Wrong queue.”);
     return mySharedVariable;
 }
Problems with Getters/
Setters

We would rather have the compiler write our getters/
setters.
  This technique works well in other methods.
Only immutable objects are fully protected this way.
Enforcing Lockless
Exclusion
- (void)accessSharedVariableAsync:(void(^)(id sharedVariable))block {

     // myQueue must be a serial queue!

     dispatch_async(myQueue, ^{

           block([self sharedVariable]);

     });

}
Enforcing Lockless
Exclusion
- (void)accessSharedVariableSyncSafe:(void(^)(id sharedVariable))block {
     // myQueue must be a serial queue!
     if (dispatch_get_current_queue() == myQueue)
        block([self sharedVariable]);
     else
        dispatch_sync(myQueue, ^{
              block([self sharedVariable]);
        });
}
Compare to
      @synchronized
[obj accessSharedDataAsync:^(id sharedVar)   @synchronized ([obj sharedVar])

{                                            {

      // Critical code here.                       // Critical code here.

}                                            }

    Fast - No Locks                              Slow - Recursive Lock

    Allows private access                        Must allow public access

    Synchronous or Asynchronous                  Synchronous Only

    Can be extended to access many shared        Only single-value access.
    data values at once.
Striding Pattern
Why Striding?

  Myth: My app will go faster if I multithread.
  Your app will only be faster if the work is
  parallelizable.
  Sometimes a block simply contains a loop, and
  each iteration can be run independently of the
  others.
  So, why not spread the iterations across many
  threads?
One Thread

dispatch_async(myQueue, ^{
      for(int i = 0; i < 10; i++)
        [self doIndependentTask:i];
});
dispatch_apply

// myQueue must be a concurrent queue!
dispatch_apply(10, myQueue, ^(size_t idx) {
      [self doIndependentTask:idx];
});
Problems with
dispatch_apply
One thread per iteration may be overkill and result in
high overhead costs.
Solution: Use striding (i.e. have one thread run many
iterations).
  Similar to loop unrolling.
  You may need to profile your app to find the ideal
  stride length.
size_t stride = 4;

size_t iterations = 10;

size_t strideCount = iterations / stride;

// myQueue must be a concurrent queue!

dispatch_apply(strideCount, myQueue, ^(size_t idx) {

       size_t i = idx * stride;

       size_t stop = i + stride;

       do {

              [self doIndependentTask:i++];

       } while (i < stop);

});

// Pick up any left over iterations.

for (size_t i = strideCount - (strideCount % stride); i < iterations; i++)

       [self doIndependentTask:i];
Continuation Pattern
What is a Continuation?


Well, it’s complicated...take CS 330.
Think of it like a completion block wrapped in a
completion block wrapped in a completion block...
Using Continuations
The following steps are optional, but either 4 or 5 must be
done.
 1. Do some processing.
 2. Wrap the completion block in another block.
 3. Copy the block if it is going to cross threads
    (unnecessary with ARC).
 4. Pass the completion block to someone else.
 5. Execute the completion block.
typedef void(^CHCompletion)(NSError * error);

- (void)uploadPhoto:(NSString *)photoPath {

     // Do some pre-processing.

     [self processPhotoAtPath:photoPath completion:^(NSError * error) {

           if (error)

               dispatch_async(dispatch_get_main_queue(), ^{

                     // Inform user of error

               });

     }];

}
- (void)processPhotoAtPath:(NSString *)path completion:
(CHCompletion)completion {

     [[completion copy] autorelease];

     // Do some resizing and add a caption, then save the modified photo
     to a temporary file for memory efficiency.

     [self uploadProcessedPhotoAtPath:newPath completion:^(NSError *
     error) {

           // Delete the temporary file.

           if (completion) completion(error);

     }];

}
- (void)uploadProcessedPhotoAtPath:(NSString *)path
completion:(CHCompletion)completion {

    [[completion copy] autorelease];

    NSError * error = nil;

    // Upload the photo.

    // Set error if something goes wrong.

    if (completion) completion(error);

}
Want to Learn More?
WWDC 2011
  Session 308
  Session 210
WWDC 2010
  Session 206
  Session 211
https://github.com/rob-brown/RBCategories/blob/master/
GCD+RBExtras.c
Questions?

Grand Central Dispatch Design Patterns

  • 1.
    Grand Central Dispatch DesignPatterns By Robert Brown @robby_brown
  • 2.
  • 3.
    Blocks Blocks are aproposed addition to C. Like a function pointer, except it also stores the context the block was created in. Similar to closures, lambdas, and anonymous functions in other languages.
  • 4.
    Blocks Declaration Syntax: returnType (^blockName)(Arguments) Blocks may be anonymous. Definition Syntax: ^ returnType (Arguments) { code; } The return type and arguments are optional. GCD provides function pointer variants to the block APIs.
  • 5.
    Blocks Blocks can modifylocal variables outside of their scope if the variables have the new __block keyword. Global and static variables can be modified without the __block keyword. Blocks automatically retain Objective-C objects, except objects that use the __block modifier. C objects must be manually retained. Beware of retain cycles.
  • 6.
  • 7.
    What is GCD? GCDis a lightweight multithreading engine. Uses a thread pool. Developers create queues of blocks rather than threads. Uses lock-less exclusion rather than mutual exclusion. Replaces blocking and polling APIs.
  • 8.
    Why Multithread ona Single Core? Keeps the UI responsive. UI code runs on the main thread. Everything else runs on a background thread. Prevents the main thread from blocking or waiting.
  • 9.
    Frequently Used APIs dispatch_async(queue,block); dispatch_once(token, block); dispatch_queue_create(name, type); dispatch_set_target_queue(object, queue); dispatch_get_global_queue(priority, flags); dispatch_release(object);
  • 10.
    Global Queues Four globalqueues: Main, Low, Default, and High. Only the main thread services the main queue. The three other queues determine the priority of background tasks. Enqueuing is thread safe.
  • 11.
  • 12.
    What is aDesign Pattern? After working on related problems, patterns often appear in the solutions. Formalized description of best practice.
  • 13.
  • 14.
    dispatch_once Guaranteed to runonly once for the lifetime of the application. Fast and thread safe. Very easy to use. Great for singletons and static class variables.
  • 15.
    The Old Way staticMyObject * myObject = nil; + myObject { @synchronized(self) { if (!myObject) myObject = [MyObject new]; } return myObject; }
  • 16.
    Problems With @synchronized @synchronized isslow. When synchronizing on the class instance, all other methods that synchronize on it will temporarily block incoming messages. You can’t synchronize on the class variable since it is initially nil. Using a custom lock also faces the initialization problem.
  • 17.
    The GCD Way staticMyObject * myObject = nil; + myObject { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (!myObject) myObject = [MyObject new]; }); return myObject; }
  • 18.
  • 19.
    Mutual Exclusion Mutual exclusionis classically handled by semaphores or locks. Both are most efficient when there is no sharing conflicts, (i.e. when you don’t need them) and don’t scale well. Queues are efficient and scale very well. In fact, queues are most efficient under high conflict.
  • 20.
    Thread-safe Access // myQueuemust be a serial queue NSLock * lock = [NSLock new]; dispatch_async(myQueue, ^{ [lock lock]; [self setMySharedVariable:42]; [self setMySharedVariable:42]; }); [lock unlock];
  • 21.
    Enforcing Lockless Exclusion - (NSInteger)mySharedVariable{ NSAssert(dispatch_get_current_queue() == myQueue, @“Wrong queue.”); return mySharedVariable; }
  • 22.
    Problems with Getters/ Setters Wewould rather have the compiler write our getters/ setters. This technique works well in other methods. Only immutable objects are fully protected this way.
  • 23.
    Enforcing Lockless Exclusion - (void)accessSharedVariableAsync:(void(^)(idsharedVariable))block { // myQueue must be a serial queue! dispatch_async(myQueue, ^{ block([self sharedVariable]); }); }
  • 24.
    Enforcing Lockless Exclusion - (void)accessSharedVariableSyncSafe:(void(^)(idsharedVariable))block { // myQueue must be a serial queue! if (dispatch_get_current_queue() == myQueue) block([self sharedVariable]); else dispatch_sync(myQueue, ^{ block([self sharedVariable]); }); }
  • 25.
    Compare to @synchronized [obj accessSharedDataAsync:^(id sharedVar) @synchronized ([obj sharedVar]) { { // Critical code here. // Critical code here. } } Fast - No Locks Slow - Recursive Lock Allows private access Must allow public access Synchronous or Asynchronous Synchronous Only Can be extended to access many shared Only single-value access. data values at once.
  • 26.
  • 27.
    Why Striding? Myth: My app will go faster if I multithread. Your app will only be faster if the work is parallelizable. Sometimes a block simply contains a loop, and each iteration can be run independently of the others. So, why not spread the iterations across many threads?
  • 28.
    One Thread dispatch_async(myQueue, ^{ for(int i = 0; i < 10; i++) [self doIndependentTask:i]; });
  • 29.
    dispatch_apply // myQueue mustbe a concurrent queue! dispatch_apply(10, myQueue, ^(size_t idx) { [self doIndependentTask:idx]; });
  • 30.
    Problems with dispatch_apply One threadper iteration may be overkill and result in high overhead costs. Solution: Use striding (i.e. have one thread run many iterations). Similar to loop unrolling. You may need to profile your app to find the ideal stride length.
  • 31.
    size_t stride =4; size_t iterations = 10; size_t strideCount = iterations / stride; // myQueue must be a concurrent queue! dispatch_apply(strideCount, myQueue, ^(size_t idx) { size_t i = idx * stride; size_t stop = i + stride; do { [self doIndependentTask:i++]; } while (i < stop); }); // Pick up any left over iterations. for (size_t i = strideCount - (strideCount % stride); i < iterations; i++) [self doIndependentTask:i];
  • 32.
  • 33.
    What is aContinuation? Well, it’s complicated...take CS 330. Think of it like a completion block wrapped in a completion block wrapped in a completion block...
  • 34.
    Using Continuations The followingsteps are optional, but either 4 or 5 must be done. 1. Do some processing. 2. Wrap the completion block in another block. 3. Copy the block if it is going to cross threads (unnecessary with ARC). 4. Pass the completion block to someone else. 5. Execute the completion block.
  • 35.
    typedef void(^CHCompletion)(NSError *error); - (void)uploadPhoto:(NSString *)photoPath { // Do some pre-processing. [self processPhotoAtPath:photoPath completion:^(NSError * error) { if (error) dispatch_async(dispatch_get_main_queue(), ^{ // Inform user of error }); }]; }
  • 36.
    - (void)processPhotoAtPath:(NSString *)pathcompletion: (CHCompletion)completion { [[completion copy] autorelease]; // Do some resizing and add a caption, then save the modified photo to a temporary file for memory efficiency. [self uploadProcessedPhotoAtPath:newPath completion:^(NSError * error) { // Delete the temporary file. if (completion) completion(error); }]; }
  • 37.
    - (void)uploadProcessedPhotoAtPath:(NSString *)path completion:(CHCompletion)completion{ [[completion copy] autorelease]; NSError * error = nil; // Upload the photo. // Set error if something goes wrong. if (completion) completion(error); }
  • 38.
    Want to LearnMore? WWDC 2011 Session 308 Session 210 WWDC 2010 Session 206 Session 211 https://github.com/rob-brown/RBCategories/blob/master/ GCD+RBExtras.c
  • 39.