因为以下代码是 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 block 之后,第一个调用者得到了满意的结果,但是第二个调用者只返回一个 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,而是local static variable,是具体的non-object variable,如这里所说的pointer,或者换个方式问,如果下面的代码在竞争线程中调用,这个"_intBuffer"是否会保证在堆中只有一个实例?
+(int)sharedIntBuffer {
static int _intBuffer = 0;
return _intBuffer;
}
最佳答案
您的代码更大的潜在问题是您正在共享一个可变集。正如 gnasher 解释的那样,您显示的使用 dispatch_once()
分配集合的代码很好,原因在那个答案中解释过。但是一旦完成,您就可能跨线程共享一个可变集。除非您采取措施同步对该集合的访问,否则您很容易遇到两个或多个线程同时更改该集合的问题。防止此类问题的最简单、最可靠的方法是将集合封装在一个类中,以确保以线程安全的方式访问集合,例如使用串行调度队列。
关于objective-c - Objective-C 上局部静态变量的线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25733091/