These following are the Concurrency API (Apple links to Concurrency Programming)

  1. pthread and NSThread,
  2. Grand Central Dispatch,
  3. NSOperationQueue,
  4. NSRunLoop.

1. Threads

Threads are “subunits of processes, which can be scheduled independently by the operating system scheduler”.

In single-code CPU, the computer assigns small slices of CPU time to each thread, creating an illusion that all threads are running concurrently.

In multi CPU core, threads are running truly in parallel (CPU view in Instruments). A normal iOS program can run on approximately 12 threads.

However, we can’t control the execution of the threads, how long a thread can be paused for other threads to execute.

  1. pthread
  2. NSThread is a simple Objective-C wrapper around pthreads.
    1. Created and start‘ed

Problem: When you create your own threads (to utilise the multi-core), the framework code also creates its own threads -> can be too many threads. Better use Queues techniques (GCD and OperationQueues). These Queues techniques centrally manage a thread pool that everyone can use.

2. Grand Central Dispatch (a ‘concurrency’ feature that is delivered based on 3 levels – language, runtime & system).

  • GCD was created to allow better use of multi-core devices.
  • With GCD, we don’t work directly with threads anymore.
  • We just add blocks of code to the queues, and GCD will automatically create and manage threads pool for us, so can avoid issue of too many threads.

GCD has 5 different queues:

  1. main queue – running on main thread
  2. 3 background queues with different priorities (High Priority, Default Priority and Low Priority)
  3. background queue (I/O throttled)

-[NSThread threadPriority] for these 5 queues:

-main- : 0.758065
DISPATCH_QUEUE_PRIORITY_HIGH : 0.532258
DISPATCH_QUEUE_PRIORITY_DEFAULT : 0.500000
DISPATCH_QUEUE_PRIORITY_LOW : 0.467742
DISPATCH_QUEUE_PRIORITY_BACKGROUND : 0.000000

Screen Shot 2015-04-01 at 5.09.14 pm

It is strongly recommended that we use Default-Priority Queues, as scheduling different priorities can lead to phenomena “priority inversion” – which means the high-priorty tasks are blocked by low-priority ones.

Most popular functions:

  • dispatch_once
  • dispatch_time
  • dispatch_after
  • dispatch_async
  • dispatch_sync
  • dispatch_get_main_queue
  • dispatch_queue_create
  • dispatch_group_enter, dispatch_group_leave, dispatch_group_async

Dispatch Objects

  • Dispatch Queues
  • Dispatch Sources
  • Dispatch Groups
  • Dispatch Semaphores

 dispatch_retain and dispatch_release

If your deployment target is at least iOS 6.0 or Mac OS X 10.8, and you have ARC disabled, you must manually retain and release your queue, either by calling dispatch_retain and dispatch_release,or by sending the queue retain and release messages (like [queue retain] and [queue release]).

3. Operation Queues

Cocoa abstraction of the GCD queues. While GCD will offer low-level control, OperationQueues have some added features on top of it -> safest and best option for Concurrency.

NSOperationQueue has 2 types:

  1. Main Queue:
    1. Run on main thread
  2. Custom Queues:
    1. Run on background thread

NSOperation: The tasks to be performed on these queues. There are 2 ways to override your operation:

  1. Override mainthe isExecuting & isFinished are managed for you.

    @implementation YourOperation
    – (void)main { // do your work here … }
    @end

  2. Override start:

    @implementation YourOperation
    – (void)start {
          self.isExecuting = YES;
          self.isFinished = NO; // start your work, which calls finished once it’s done …
    }
    – (void)finished {
          self.isExecuting = NO;
          self.isFinished = YES;
    }
    @end

Easy to add to Queue:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
YourOperation *operation = [[YourOperation alloc] init];
[queue addOperation:operation];

Also we can add blocks to queue (very convenient)

[[NSOperationQueue mainQueue] addOperationWithBlock:^{ // do something… }];

We can also specify how many queues can be run concurrently: by setting maxConcurrentOperationCount for the operation queue. Setting 1 => we have a serial queue.

We can also specify the order of operations by setting dependencies:

[middleOperation addDependency: operation1];
[middleOperation addDependency: operation2];
[lastOperation addDependency: middleOperation];

operation1 and operation2 will execute before middleOperation, and the lastOperation will be last.

4. Run Loops

Run loop is something bound to a particular thread. While Run Loop is not a concurrency mechanism, it can be used to schedule tasks to run asynchronously.

The main thread always have a main run loop running, it handles UI Events and Timers. So the main runloop is used when:

  • Schedule a Timer
  • Use NSURLConnection
  • performSelector:withObject:afterDelay:

Runloop has many modes: (string)

  • NSDefaultRunLoopMode    // the default mode, it is a member of the CommonModes.
  • NSRunLoopCommonModes   // a group of modes, it allows the source/timer/observer to register to many modes. 

So if we want to have Timers fired when scrolling, we should add timer to thread in NSRunLoopCommonModes.

The runloop can only run the sources/timers/observers that is registered with the current mode it’s running.  The default mode is a member of the CommonModes.

Only the main thread will always have a run loop. Other threads need to enable the run loop – but we rarely need to use it – we better use the main run loop.

5. Challenges of Concurrent Programming

  1. Sharing of resources (both read & write)
  2. Mutual Exclusion: you may need to write code to lock a resource, which can cause overheads.
  3. Dead Locks: both A and B need 2 resource x & y, but A locks x and B locks y, so deadlock.
  4. Starvation: usually read is allowed as long as there are no write lock, so a writing threads can be starved if there are too many reading threads (can solve by giving writing threads priority, or use read/copy/update)
  5. Priority Inversion: a low-priority thread is holding a lock on X got pre-empted by a medium-priority thread, but not releasing lock, so the highest-priority thread can’t be run as it also need to lock X. Basically then all the medium-priority threads will run before both the low and high-priority threads.

6. Safe pattern to use: NO LOCK NEEDED

  • pull out the data (you want) on the main thread,
  • then use an operation queue to do the actual work in the background,
  • get back to the main queue to deliver the result of your background work

REFERENCES:

  1. Low-level API
  2. Best Practices for Background tasks
About these ads