Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread safety of local static variable on Objective-C

As the following code is quite a common pattern on Objective-C for create an instance and make sure it is thread safe. However this thread safe is based on one important condition that the local static variable is thread safe guaranteed by compiler, which means the static _sharedCache pointer will be guaranteed created within a thread safe way, however I can't find out any documentation mention about this. Is anybody can provide me more confident proof? (As people here keep focus on the mutable set I was used at beginning, so I just change it to a NSCache, that's really not the point. I'm talking about the thread safety about creating local static pointer here (not the instance this pointer is pointing to))

+ (NSCache*)sharedCache {
  static NSCache* _sharedCache = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _sharedCache = [[NSCache alloc] init];
  });
  return _sharedCache;
}

Just in case I did not described the race condition clear, consider two threads simultaneously call this API, and simultaneously check whether this static _sharedCache pointer is existed, if not, they gonna create a fresh static pointer by their-self. Then it might be two static pointer of NSMutableSet here, even the dispatch_once guaranteed the instance initialize was only happened once, that is only for one of these two static pointers, and then after the dispatch_once block, the first caller get a satisfied result, but the second caller just get a nil pointer back;

Consider this case, A and B are two threads, entered this code simultaneously, A check the static pointer "_sharedSet" here and found there's no such pointer here,(pointer is an instance of unsigned long), so it create one and assign 0 to it, now this pointer is stored in memory address (0xAAAAAAAA), simultaneously B did the same behavior, create a static pointer in heap but with memory address(0xBBBBBBBB), then the dispatch_once blocked both threads until it finished, so it assigned a new pointer value with that created NSSet instance and assigned this value to address 0xAAAAAAAA,however the value on address 0xBBBBBBBB still 0, because nobody update it, so it the thread B just return a nil. So basically my question is not about doubt the dispatch_once, but double about the thread safety about create local static variable, this is a valid issue on C++ until C11 resolved it. I just wonder whether Clang has noticed this issue too.

And this question is really not about dispatch_once, it's about local static variable, be specific non-object variable, as the pointer mentioned here, or just change a way to ask, if the following code called in competed threads, whether this "_intBuffer" will be guaranteed only one instance in the heap?

    +(int)sharedIntBuffer {
        static int _intBuffer = 0;
        return _intBuffer;
    }
like image 973
Pei Avatar asked Dec 25 '22 04:12

Pei


1 Answers

The bigger potential problem with your code is that you're sharing a mutable set. As gnasher explains, the code you're showing to allocate the set using dispatch_once() is fine, for the reasons explained in that answer. But once that's done, you're sharing a mutable set potentially across threads. Unless you take steps to synchronize access to that set, you could easily have problems where two or more threads are changing the set at the same time. The easiest, most reliable way to prevent those kinds of problems are to encapsulate the set in a class that ensures that the set is accessed in a thread-safe manner, e.g. using a serial dispatch queue.

like image 186
Caleb Avatar answered Dec 28 '22 05:12

Caleb