Tuesday, April 16, 2013

Using libdispatch for Critical Sections and Synchronization

libdispatch is the new foundation of concurrent programming on iOS and OS X. Most people look to libdispatch to asynchronously run blocks of code. However, even in code based on pthreads and NSThreads, you can use dispatch queues for synchronization where you might otherwise use pthread mutex or NSLock objects. Let's look at two contrived examples and talk about the differences. The locking is highlighted in red.

Using an NSRecursiveLock.

- (void) syncLockExample
{    
    for (unsigned i = 0; i < 1000; i++)
    {
        int r = rand();
        
        [_recursiveLock lock];
        
        // add a value if in the lower half of the range.
        if (r <= RAND_MAX/2)
            [_dataArray addObject:@(r)];
        
        // if the value is odd, remove it.
        if ([[_dataArray lastObject] intValue] % 2)
            [_dataArray removeLastObject];
        
        [_recursiveLock unlock];
    }
}

Using a dispatch queue:

- (void) syncQueueExample
{    
    for (unsigned i = 0; i < 1000; i++)
    {
        int r = rand();
        
        dispatch_sync(_dispatchQueue, ^{
            // add a value if in the lower half of the range.
            if (r <= RAND_MAX/2)
                [_dataArray addObject:@(r)];
            
            // if the value is odd, remove it.
            if ([[_dataArray lastObject] intValue] % 2)
                [_dataArray removeLastObject];
        });
    }
}

At first blush, there isn't much difference — hardly worth blogging about. However, these are trivial examples. From a maintenance perspective, the dispatch queue approach seems much better. Nobody can insert a return statement or remove a line of code that results in a lock left in the locked state, likely resulting in a deadlock. Aesthetically, the dispatch queue approach is better because the critical section is a block, a well-defined construct supported by the language (much like @synchronized). The lock version uses no feature of the language to define its scope.

There are some disadvantages to the dispatch queue approach. NSRecursiveLock, which is the NSLock I would normally choose for these scenarios, is forgiving of code in the critical section that itself calls more critical sections that share the same lock. Be warned that if you do the same with dispatch_sync, you will deadlock. However, if you're calling big swatches of code in such a synchronized state, you may want to rethink your locking altogether.