Share
Embed
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
Transcript
38 slides
-
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. -
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. -
What is a Design Pattern? After working on related problems,
patterns often appear in the solutions. Formalized description of best practice. -
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. -
static MyObject * myObject = nil; + myObject { @synchronized(self)
{ if (!myObject) myObject = [MyObject new]; } return myObject; } The Old Way -
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. -
static MyObject * myObject = nil; + myObject { static
dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (!myObject) myObject = [MyObject new]; }); return myObject; } The GCD Way -
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 dispatch_async(myQueue,
^{ [self setMySharedVariable:42]; }); NSLock * lock = [NSLock new]; [lock lock]; [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]); }); } -
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. -
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]; -
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