Reputation: 784
We want to guarantee thread safety for a static variable. We used another static variable as object in the @synchronized directive. Like this:
static NSString *_saveInProgressLock = @"SaveInProgressLock";
static BOOL _saveInProgress;
+ (BOOL)saveInProgress {
@synchronized(_saveInProgressLock) {
return _saveInProgress;
}
}
+ (void)setSaveInProgress:(BOOL)save {
@synchronized(_saveInProgressLock) {
_saveInProgress = save;
}
}
We are experiencing issues in the app currently on the store, which may be reproduced by preventing the _saveInProgress variable to be set to NO. Do you see any problem with the above code?
How does it differ from this?
static BOOL _saveInProgress;
+ (BOOL)saveInProgress {
@synchronized([MyClass class]) {
return _saveInProgress;
}
}
+ (void)setSaveInProgress:(BOOL)save {
@synchronized([MyClass class]) {
_saveInProgress = save;
}
}
Upvotes: 0
Views: 456
Reputation: 6517
tl;dr: This is perfectly safe as long as the string literal is unique. If it is not unique there may be (benign) problems, but usually only in Release mode. There may be an easier way to implement this, though.
@synchronized
blocks are implemented using the runtime functions objc_sync_enter
and objc_sync_exit
(source). These functions are implemented using a global (but objc-internal) side table of locks that is keyed by pointer values. On the C-API-level, you could also lock on (void *)42
, or in fact any pointer value. It doesn't even matter wether the object is alive, because the pointer is never dereferenced. However, the objc compiler refuses to compile a @synchronized(obj)
expression if the obj
does not statically typecheck to an id
type (of which NSString *
is a subtype, so it's okay) and maybe it retains the object (I'm not sure about that), so you should only use it with objects.
There are two critical points to consider though:
obj
on which you synchronize is the NULL-pointer (nil
in Objective C), then objc_sync_enter
and objc_sync_exit
are no-ops, and this leads to the undesirable situation where the block is performed with absolutely no locking.@synchronized
blocks, the compiler may be smart enough to map them to the same pointer address. Maybe the compiler does not do this now, but it is a perfectly valid optimization that Apple may introduce in the future. So you should make sure that you use unique names. If this happens, two different @synchronized
blocks may accidentally use the same lock where the programmer wanted to use different locks. By the way, you can also use [NSObject new]
as a lock object.Synchronizing on a class object ([MyClass class]
) is perfectly safe and okay too.
Now for the easier way. If you just have a single BOOL variable that you want to be atomic, you may use lock-free programming:
static BOOL _saveInProgress;
+ (BOOL)saveInProgress {
__sync_synchronize();
return _saveInProgress;
}
+ (void)setSaveInProgress:(BOOL)save {
_saveInProgress = save;
__sync_synchronize();
}
This has much better performance and is just as threadsafe. __sync_synchronize()
is a memory barrier.
Note however, that the safety of both solutions depend on how you use them. If you have a save-method somewhere that looks like this:
+ (void)save { // line 21
if(![self saveInProgress]) { // line 22
[self setSaveInProgress:YES]; // line 23
// ... do stuff ...
[self setSaveInProgress:NO]; // line 40
}
}
that +save
method is not threadsafe at all, because there is a race condition between line 22 and line 23. (Don't wanna go into details here.. just ask a new question if you need more information.)
Upvotes: 3