Saturday, September 17, 2011

Lockless Exclusion Accessor Pattern

With all the work I have been doing with blocks and Grand Central Dispatch (GCD), I have developed a useful design pattern for handling mutual exclusion. I haven’t seen any patterns quite like this one, so I’ll claim it as my own.

Mutual exclusion is always a challenge when multithreading any application. GCD makes mutual exclusion substantially easier by using queues instead of locks. Nevertheless, there are still some challenges. When creating shared data I often include a warning in my documentation that it must only be accessed within a certain serial GCD queue. However, other developers that use my code may not read my documentation. Even I forget occasionally that some particular data isn’t thread safe. So, how do we make this easier? This is a situation for what I call the Lockless Exclusion Accessor pattern. I’ll call it LEA for short. (I also thought of calling it a “letter” since it’s closely related to a getter, but that seemed too silly and generic.)

Before going into the details of LEAs, we need to first understand why encapsulation is important. Most object-oriented languages have the ability to make variables private so they can only be directly accessed internal to the class. Limited access is given to the outside through getters and setters. This prevents classes on the outside making arbitrary changes to another classes internal data. To see how to encapsulate @propertys in Objective-C check out my @property article. We will apply the same principles to our shared data.

First, move your shared data into a class extension so it will not be externally visible. Second, you will want to typdef your block type. This keeps your code cleaner and easier to read. For this example, I’m going to use an NSManagedObjectContext as my shared data. So, my block typedef looks like this:

typedef void(^RBMOCBlock)(NSManagedObjectContext * moc);

Next, you need to create an accessor method. Outside classes will only be able to access your shared data through this method. It will look something like this:

- (void)accessMOCAsync:(RBMOCBlock)block {
        dispatch_async(dispatch_get_main_queue(), ^{
                block([self managedObjectContext]);
        });
}

The dispatch_async takes the passed in block and runs it on the main queue. We could (and really should) modify this code to run on a private serial queue instead. We also pass the shared data to the block. This grants the block exclusive access to that data (as long as the dispatch queue is a serial queue). You’ll also notice that this is an asynchronous call. If needed, we can also make a synchronous LEA. It looks nearly identical:

- (void)accessMOCSync:(RBMOCBlock)block {
        dispatch_sync(dispatch_get_main_queue(), ^{
                block([self managedObjectContext]);
        });
}

Now that we’ve built our asynchronous and synchronous LEAs, let’s use one.

[object accessMOCAsync:^(NSManagedObjectContext * context) {
        // Critical code goes here.
}];

That’s all there is to it. Just wrap your critical code in a block and you get exclusive, local access to the NSManagedObjectContext. LEAs are perfect for Core Data MOCs since they are not thread safe and are often the cause of much grief.

LEAs are also quite flexible. For example, you can merge multiple LEAs into one like this:

[object accessMOCAsync:^(NSManagedObjectContext * context, NSDictionary * userInfo) {
        // Critical code goes here.
}];

The above LEA gives the block exclusive access to two shared variables at once. You can extend this as far as you want. This way to don’t need to write multiple LEAs per class and you don’t need to nest LEAs. However, this comes with a tradeoff. You gain convenience at the cost of more contention for shared data.

Now, LEAs don’t perfectly protect shared data. It’s as good as Objective-C can get though. Rogue developers could pass the pointer to the MOC out of the block. They could also use the dynamic runtime to call the internal getters and setters. However, any developer doing either of these things is asking for a lot of trouble and should not be permitted to write Objective-C code.

For more GCD and block design patterns, check out these sources.


UPDATE:

I've removed all references to SyncSafe due to problems I've discovered.