
This is the sixth and final post in a series about Grand Central Dispatch.
Long time users of GCD will tell you: it’s easy to forget where you are. Which queue am I on? Should I dispatch_sync
to a queue that protects my variables, or has my caller taken care of that?
In this post I will describe a simple naming convention that has kept me sane over the years. Follow it, and you shouldn’t ever deadlock or forget to synchronize access to your member variables again.
Designing thread-safe libraries
When designing thread-safe code, it helps to have a library mindset. You should distinguish between the external or public interface (API), and the internal or private interface. The public API is presented in public headers, and the private interface is presented in private headers, used only by the library’s developers.
The ideal thread-safe public API does not expose threading or queueing at all (unless, of course, thread or queue management is the point of your library’s utility). It should simply not be possible to induce a race condition or deadlock when using your library. Let’s take a look at this classic example:
// Public header
#import <Foundation/Foundation.h>
// Thread-safe
@interface Account: NSObject
@property (nonatomic, readonly, getter=isClosed) BOOL closed;
@property (nonatomic, readonly) double balance;
- (void)addMoney:(double)amount;
- (void)subtractMoney:(double)amount;
- (double)closeAccount; // Returns balance
@end
@interface Bank: NSObject
@property (nonatomic, copy) NSArray<Account *> *accounts;
@property (nonatomic, readonly) double totalBalanceInAllAccounts;
- (void)transferMoneyAmount:(double)amount
fromAccount:(Account *)fromAccount
toAccount:(Account *)toAccount;
@end
If it weren’t for the helpful comment, you wouldn’t be able to tell from this header that the library is thread-safe. In other words, you’ve defined thread-safety as an implementation detail.
Read more…