I'm reading the Apple docs around Thread Safety, and it's not completely clear to me what (in practice) really constitutes a class being thread safe. To help understand this better, what would would all need to be done to the following class to make it thread safe (and why)?
#import "UnsafeQueue.h"
@interface UnsafeQueue()
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation UnsafeQueue
- (id)peek {
return [self.data firstObject];
}
- (NSUInteger)length {
return [self.data count];
}
- (void)enqueue:(id)datum {
[self.data addObject:datum];
}
// other methods omitted...
@end
Would simply creating an ivar NSLock and then lock/unlock surrounding all interactions with the underlying NSMutableArray?
Does the length method, which is just asking for the array's count, need to do this as well?
To make these classes thread-safe, you must prevent concurrent access to the internal state of an instance by more than one thread. Because Java was designed with threads in mind, the language provides the synchronized modifier, which does just that.
By creating a queue of tasks where only one task can be processed at any given time, you are indirectly introducing thread safety to the component that is using the queue. The greatest feature of DispatchQueue is how it completely manages any threading-related tasks like locking and prioritization for you.
An object is said to be thread safe if more than one thread can call methods or access the object's member data without any issues; an "issue" broadly being defined as being a departure from the behaviour when only accessed from only one thread.
No, static functions are not inherently thread-safe. Even your simple example isn't. Assuming both intvariable and stringvariable are supposed to be updated at the same time, another thread could observe the state of c1 between the two assignments, leading to data corruption.
The easiest and best way to make a class thread safe is to make it immutable. Then you don't have to deal with any of this. It just works. It really is worth your time to think about whether you need mutability on multiple threads.
But if an immutable class creates significant problems for your design, then generally the best way to implement it is with GCD rather than locks. GCD has much lower overhead and is generally speaking easier to get right.
In this particular case, I'd implement it along these lines (untested, and I've been working in Swift for a while now, so forgive me if I drop a semicolon):
#import "SafeQueue.h"
@interface SafeQueue()
@property (strong, nonatomic) NSMutableArray *data;
@property (strong, nonatomic) dispatch_queue_t dataQueue;
@end
@implementation SafeQueue
- (instancetype)init {
if (self = [super init]) {
_dataQueue = dispatch_queue_create("SafeQueue.data", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (id)peek {
__block id result = nil;
dispatch_sync(self.dataQueue, ^{ result = [self.data firstObject] });
return result;
}
- (NSUInteger)length {
__block NSUInteger result = 0;
dispatch_sync(self.dataQueue, ^{ result = [self.data count] });
return result;
}
- (void)enqueue:(id)datum {
dispatch_barrier_async(self.dataQueue, ^{ [self.data addObject:datum] });
}
// other methods omitted...
@end
Note the use of dispatch_sync
for all readers and dispatch_barrier_async
for all writers. This is how you keep your overhead to a minimum by allowing parallel readers and exclusive writers. And if there is no contention (which is the normal case), dispatch_sync
has much lower overhead than a lock (NSLock
or @synchronized
or even a pthreads lock).
See Migrating Away from Threads for more advice from Apple on how to better deal with concurrency in Cocoa.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With