【发布时间】:2014-09-08 21:01:35
【问题描述】:
因为下面的代码是 Objective-C 上非常常见的模式,用于创建实例并确保它是线程安全的。 然而,这个线程安全基于一个重要条件,即编译器保证本地静态变量是线程安全的,这意味着静态_sharedCache 指针将保证以线程安全的方式创建,但是我不能找出有关此的任何文档。有谁能给我更自信的证据吗? (由于这里的人们一直关注我一开始使用的可变集,所以我只是将其更改为 NSCache,这真的不是重点。我在这里谈论的是关于创建本地静态指针的线程安全(不是这个指针指向的实例))
+ (NSCache*)sharedCache {
static NSCache* _sharedCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedCache = [[NSCache alloc] init];
});
return _sharedCache;
}
万一我没有把竞态条件描述清楚,考虑两个线程同时调用这个API,同时检查这个静态_sharedCache指针是否存在,如果不存在,它们会自己创建一个新的静态指针。那么这里可能是 NSMutableSet 的两个静态指针,即使 dispatch_once 保证实例初始化只发生一次,也就是只针对这两个静态指针之一,然后在 dispatch_once 块之后,第一个调用者得到一个满意的结果,但是第二个调用者只得到一个 nil 指针;
考虑这种情况,A和B是两个线程,同时进入这段代码,A查看这里的静态指针“_sharedSet”,发现这里没有这个指针,(指针是unsigned long的实例),所以创建一个并将0分配给它,现在这个指针存储在内存地址(0xAAAAAAAA)中,同时B做了同样的行为,在堆中创建一个静态指针但内存地址(0xBBBBBBBB),然后dispatch_once阻塞两个线程直到它完成,所以它为创建的 NSSet 实例分配了一个新的指针值,并将该值分配给地址 0xAAAAAAAA,但是地址 0xBBBBBBBB 上的值仍然为 0,因为没有人更新它,所以线程 B 只返回一个 nil。所以基本上我的问题不是怀疑 dispatch_once,而是关于创建局部静态变量的线程安全性,这是 C++ 上的一个有效问题,直到 C11 解决它。我只是想知道 Clang 是否也注意到了这个问题。
而且这个问题真的不是dispatch_once,是局部静态变量,是具体的非对象变量,就像这里提到的指针,或者换个方式问,如果下面的代码在竞争线程中调用,是否保证这个 "_intBuffer" 在堆中只有一个实例?
+(int)sharedIntBuffer {
static int _intBuffer = 0;
return _intBuffer;
}
【问题讨论】:
-
NSMutableSet 不是线程安全的。所以,不,上面的代码不是线程安全的,但不是出于您担心的原因。请参阅下面@Caleb 的回答。
-
非常感谢您的重播,但这不是我的意思,在开头列出不正确的演示是我的错。我刚刚更新了我的问题以避免进一步的误解
标签: objective-c multithreading race-condition static-variables