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

Concurrency on iOS and OSX

March 24, 2014

Concurrency on iOS and OSX

Small introduction to concurrency on iOS and OSX


March 24, 2014

More Decks by yageek

Other Decks in Programming


  1. Topics 1. Basics about threading 2. Run loops in iOS/Mac

    application 3. Concurrency 4. Thread Pool Pattern 5. Grand Central Dispatch 6. NSOperation 2 / 36
  2. Basics on threading Two scheduling units on most widespread operating

    systems: Processes: each unit has its own virtual memory Threads: sub-units in process that shared virtual memory Source : Modern Operating Systems - Andrew S. Tannenbaum 4 / 36
  3. Basics on threading A specific amount of time is allowed

    to units by the scheduler : The unit runs, sleeps, wakes up, sleeps, ..., stops The strategy used depends on the operating system. Multi-tasks systems often use a variant of Round- Robin with priorities. It is a time based multiplexer algorithm. Source : Modern Operating Systems - Andrew S. Tannenbaum 5 / 36
  4. Basics on threading Costs Each thread/process needs opaque kernel and/or

    userspace memory. Context switching and thread creation costs time. Item Approximate cost Kernel data structures Approximately 1 KB Stack space 512 KB (secondary threads) 8 MB (OS X main thread) 1 MB (iOS main thread) Creation time Approximately 90 microseconds Source : Threading Programming Guide 6 / 36
  5. Basics on threading Costs Multicore systems Most of the latest

    processors have several cores : Threads are really parallelized Using thread in encouraged Using too many threads could leads to threading overhead Source : The overhead of spawning threads (a performance experiment) - Matt Gallagher 7 / 36
  6. Basics on threading Costs Multicore systems Synchronisation General C tools

    used for process/thread communication : Semaphore/Mutex Spinlock Message passing Bad using of thoses tools could lead to : Race conditions Deadlock 8 / 36
  7. Run loops in iOS/Mac application Reactor Pattern A run loop

    is a single threaded loop that dispatch events synchronously to theirs handlers. This thread is generally called the main thread On BSD/Darwin, it is based on kqueue, on others systems, it relies on select, poll or eppoll system calls Idea : # -> No busy wait due to system calls or kqueue while there are still events to process: e = get the next event if there is a callback/handler associated with e: call the callback/handler For more informations : http://en.wikipedia.org/wiki/Reactor_pattern An introduction to libuv 10 / 36
  8. Run loops in iOS/Mac application Reactor Pattern NSRunLoop Apple provides

    its own abstraction of run loop using the NSRunLoop class. Each thread has a run loop, but only the main thread starts it automatically. 11 / 36
  9. Run loops in iOS/Mac application Reactor Pattern NSRunLoop NSRunLoop Sources

    Handlers for events are created using the CFRunLoopSourceRef object. For input events as mouse or keayboard events, NSPort, NSConnection and NSTimer, the corresponding CFRunLoopSourceRef are managed automatically. It's possible to create custom sources of events : Threading Programming Guide - Configuring Run Loop Sources 12 / 36
  10. Concurrency Concurrency vs Parallelism Concurrency : Programming as the composition

    of independently executing processes. Parallelism : Programming as the simultaneous execution of (possibly related) computations. A talk on the subject by Rob Pike (golang team) : Concurrency vs Parallelism 14 / 36
  11. Concurrency Concurrency vs Parallelism Usual needs At application level, needs

    are usually the same : Downloading data asynchronously and notify the GUI Compute heavy data in background and notify the GUI Synchronize several concurrent tasks at some point of execution Ensure thread-safety of API Example of patterns to use : Producer/Consumer Working Queue Message passing 15 / 36
  12. Concurrency Concurrency vs Parallelism Usual needs Core Foundation Core Foundation

    tools to implement them : NSThread, [NSThread detachNewThreadSelector:toTarget:withObject:] [NSObject performSelectorInBackground:withObject:], [NSObject performSelectorOnMainThread:withObject:] NSLock NSRecursiveLock NSConditionLock NSDistributedLock NSCondition @synchronize(lock) statement Atomic operations OSAtomicOr32, OSAtomicAdd32,... POSIX threads, conditions and mutex Drawbacks : You may feel to reinvent the wheel Boilerplate code Synchronisation with the main thread ? Code not always easy to read/understand Easy to enter in race conditions Efficiency vs speed in coding ? 16 / 36
  13. Concurrency Usual needs Core Foundation Example 1 Producer/Consumer /* Producer

    */ id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA]; while(true) { [condLock lock]; /* Add data to the queue. */ [condLock unlockWithCondition:HAS_DATA]; } /* Consumer */ while (true) { [condLock lockWhenCondition:HAS_DATA]; /* Remove data from the queue. */ [condLock unlockWithCondition:(isEmpty ? NO_DATA : HAS_DATA)]; // Process the data locally. } 17 / 36
  14. Concurrency Concurrency vs Parallelism Usual needs Core Foundation Example 1

    Example 2 Download an URL asynchronously in the background and get notified in the main thread ? (before iOS 5) /* AFNetworking : https://github.com/AFNetworking/AFNetworking */ @implementation AFURLConnectionOperation /* Call in NSThread initWithTarget:selector:object */ + (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } - (void)start { /* ... */ [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil]; /* ... */ } - (void)operationDidStart { self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; for (NSString *runLoopMode in self.runLoopModes) { [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; } [self.connection start]; } } 18 / 36
  15. Thread pool pattern Only two notions : tasks and queues

    The thread pool pattern is an abstraction over threads that helps to target on the essential : Number of thread managed by the framework Number of thread otpimized for the system Banning thread from the semantic Examples : QThreadPool in Qt framework Parallel Extensions in .NET GCD and NSOperation on Darwin 20 / 36
  16. Grand Central Dispatch API GCD was developped by Apple Inc.

    and released in the libdispatch library. libdispatch is open source C API Block oriented (Objective-C2.0) Queues represented by a unique structure : dispatch_queue_t Tasks are represented by blocks Premption capability with priorities Optimized synchronisation tools (I/O, Buffer, General) Terse code 22 / 36
  17. Grand Central Dispatch API Queues Queues are either concurrent or

    serial Serial queue : tasks are executed one a time in FIFO style. Concurrent queue : tasks are runned concurrently For each application, the OS provide default queues : One main queue : queue associated with the main thread Four concurrent queues with descending priorities 23 / 36
  18. Create queues /* Get the main thread queue */ dispatch_queue_t

    * mainQueue = dispatch_get_main_queue(); /* Get globals concurrent queue */ dispatch_queue_t * concurrent = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0); dispatch_queue_t * concurrent2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); dispatch_queue_t * concurrent3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0); dispatch_queue_t * concurrent4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0); /* Create new Serial */ dispatch_queue_t * serial = dispatch_queue_create("com.company.myapp", DISPATCH_QUEUE_SERIAL ); dispatch_queue_t * serial2 = dispatch_queue_create("com.company.myapp", NULL ); /* Create new concurrent queue (Since iOS 5) */ dispatch_queue_t * concurrent5 = dispatch_queue_create("com.company.myapp", DISPATCH_QUEUE_CONCURRENT ); 24 / 36
  19. Grand Central Dispatch API Queues Tasks Representing tasks with blocks

    with dispatch_block_t: typedef void (^dispatch_block_t)(void); dispatch_block_t block_task = ^{ printf("Task1 \n");} Representing tasks with functions with dispatch_function_t: typedef void (*dispatch_function_t)(void*) void function_task(void * context){ printf("Task with function \n"); } Enqueueing task is either synchronously or asynchronously: //Return immediatly dispatch_async(myQueue,block_task); dispatch_async_f(myQueue,function_task); //Wait for task to end dispatch_sync(myQueue,block_task); dispatch_sync_f(myQueue,function_task); You're encouraged to use blocks and asynchronous enqueueing as often as possible. 25 / 36
  20. Grand Central Dispatch API Queues Tasks Other cool stuffs :

    dispatch_apply,dispatch_apply_f : execute task multiple times dispatch_after,dispatch_after_f: execute a task after a specified amount of time dispatch_once : execute the task only once during the application life-cycle 26 / 36
  21. Grand Central Dispatch API Queues Tasks Dispatch group Regroups tasks

    in a set to synchronize. It is based on a counter of outstanding tasks dispatch_group_t group = dispatch_group_create(); dispatch_block_t task1 = ^{ printf("Task 1\n");}, task2 = ^{ printf("Task 2\n");}, task3 = ^{ NSInteger i; for(i = 0; i < INT16_MAX; ++i); }; dispatch_queue_t queue; queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); dispatch_group_async(group,queue,task1); dispatch_group_async(group,queue,task2); dispatch_group_async(group,queue,task3); /* Wait for the oustanding tasks counter to be null*/ dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 27 / 36
  22. Grand Central Dispatch API Queues Tasks Dispatch group Manually force

    increment/decrement the outstanding tasks counter with dispatch_group_enter and dispatch_group_leave dispatch_group_enter(group); dispatch_group_async(group,queue,^{ awesome_function_with_callback(^{ /* ... */ dispatch_group_leave(group); }) }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); Schedule a block when all previous committed blocks end with dispatch_group_notify dispatch_group_notify(group, queue, ^{ printf("I am called at the end !\n");} ); 28 / 36
  23. Grand Central Dispatch API Queues Tasks Dispatch group Barriers Barriers

    offers a very elegent way to synchronize tasks within a concurrent queue : dispatch_block_t readTask1 = ^{ printf("Reading ...\n");}, readTask2 = ^{ printf("Reading ...\n");}, readTask3 = ^{ printf("Reading ...\n");}, writeTask4 = ^{printf("Writing \n");}; dispatch_queue_t queue; queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); dispatch_async(queue,readTask1); dispatch_async(queue,readTask2); dispatch_barrier_async(queue,writeTask4); dispatch_async(queue,readTask3); 29 / 36
  24. Grand Central Dispatch API Queues Tasks Dispatch group Barriers More

    Lot of others features : Target Queue Semaphores Dispatch sources Queue Data Dispatch I/O See : Concurrency Programming Guide NSBlog - Topics on GCD CocoaSamurai - Guide to blocks and grand central dispatch WWDC 2011 - Mastering Grand Central Dispatch WWDC 2010 - Introducing Blocks and Grand Central Dispatch on iPhone WWDC 2010 - Simplifying iPhone App Development with Grand Central Dispatch 30 / 36
  25. NSOperation Standing on the shoulders of a giant NSOperation is

    built on top of GCD : Higher-level of abstraction Objective-C API Ready to use with Apple Framework : UIKit, AVFoundation, MapKit, OpenGL, ... Small API - Enough for most basic cases All magic is hidden in two objects : NSOperation : represents tasks NSOperationQueue : represents queue 32 / 36
  26. NSOperation Standing on the shoulders of a giant NSOperationQueue Create

    queues /* Get Main Queue */ NSOperationQueue * mainQueue = [NSOperationQueue mainQueue]; /* Create Serial Queue */ NSOperationQueue * serialQueue = [[NSOperationQueue alloc] init]; serialQueue.name = @"com.company.app.serial"; serialQueue.maxConcurrentOperationCount = 1; /* Create Concurrent Queue */ NSOperationQueue * concurrentQueue = [[NSOperationQueue alloc] init]; 33 / 36
  27. NSOperation Standing on the shoulders of a giant NSOperationQueue NSOperation

    Abtract class representing a task. Need to subclass -> See documentation Manage queue and thread priorities Completion block feature Summerize : 1. Implement start , main , isConcurrent , isFinished and isExecuting methods 2. Test for cancel events in your main and start methods when necessary 3. Add operation to a queue 34 / 36
  28. NSOperation Standing on the shoulders of a giant NSOperationQueue NSOperation

    Example NSOperationQueue * concurrentQueue = [[NSOperationQueue alloc] init]; MyCustomOperation* op = [[MyCustomOperation alloc] init]; [concurrentQueue addOperationWithBlock:^{ printf("Hello \n");}]; [concurrentQueue addOperationWithBlock:^{ printf("World \n");}]; [concurrentQueue addOperation:op]; [concurrentQueue waitUntilAllOperationsAreFinished]; 35 / 36
  29. NSOperation Standing on the shoulders of a giant NSOperationQueue NSOperation

    Example Synchronisation Easy to synchronise tasks with dependency : NSBlockOperation * a = [NSBlockOperation blockOperationWiBlock:^{ printf("A\n");}]; NSBlockOperation * b = [NSBlockOperation blockOperationWiBlock:^{ printf("B\n");}]; NSBlockOperation * c = [NSBlockOperation blockOperationWiBlock:^{ printf("C\n");}]; [a addDependency:b]; [b addDependency:c]; [c.completionBlock:^{ printf("End of C\n");}]; NSOperationQueue * queue = [[NSOperationQueue alloc] init]; [queue addOperation:a]; [queue addOperation:b]; [queue addOperation:c]; 36 / 36