Slide 1

Slide 1 text

Concurrency on iOS and OSX Thread, GCD & NSOperation [yageek] 1 / 36

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Basics on threading 3 / 36

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Run loops in iOS/Mac application 9 / 36

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Concurrency 13 / 36

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Thread pool pattern 19 / 36

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Grand Central Dispatch 21 / 36

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

NSOperation 31 / 36

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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