Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Grand Central Dispatch Design Patterns

Rob Brown
September 08, 2011

Grand Central Dispatch Design Patterns

Grand Central Dispatch (GCD) and blocks make multi-threading easy. However, there are some lesser-known techniques that benefit your next project.

Rob Brown

September 08, 2011
Tweet

More Decks by Rob Brown

Other Decks in Programming

Transcript

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. What is a Design Pattern? After working on related problems,

    patterns often appear in the solutions. Formalized description of best practice.
  8. 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.
  9. static MyObject * myObject = nil; + myObject { @synchronized(self)

    { if (!myObject) myObject = [MyObject new]; } return myObject; } The Old Way
  10. 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.
  11. static MyObject * myObject = nil; + myObject { static

    dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (!myObject) myObject = [MyObject new]; }); return myObject; } The GCD Way
  12. 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.
  13. Thread-safe Access // myQueue must be a serial queue dispatch_async(myQueue,

    ^{ [self setMySharedVariable:42]; }); NSLock * lock = [NSLock new]; [lock lock]; [self setMySharedVariable:42]; [lock unlock];
  14. 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.
  15. Enforcing Lockless Exclusion - (void)accessSharedVariableAsync:(void(^)(id sharedVariable))block { // myQueue must

    be a serial queue! dispatch_async(myQueue, ^{ block([self sharedVariable]); }); }
  16. Compare to @synchronized [obj accessSharedDataAsync:^(id sharedVar) { // Critical code

    here. } Fast - No Locks Allows private access Synchronous or Asynchronous Can be extended to access many shared data values at once. @synchronized ([obj sharedVar]) { // Critical code here. } Slow - Recursive Lock Must allow public access Synchronous Only Only single-value access.
  17. 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?
  18. One Thread dispatch_async(myQueue, ^{ for(int i = 0; i <

    10; i++) [self doIndependentTask:i]; });
  19. 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.
  20. 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];
  21. 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...
  22. 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.
  23. 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 }); }]; }
  24. - (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); }]; }
  25. - (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); }
  26. 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