Objective-C上局部静态变量的线程安全性

Pei*_*Pei 4 multithreading objective-c static-variables race-condition

由于以下代码是Objective-C上非常常见的模式,用于创建实例并确保它是线程安全的.但是这个线程安全是基于一个重要的条件,即本地静态变量是由编译器保证的线程安全,这意味着静态_sharedCache指针将保证以线程安全的方式创建,但是我无法找到任何关于此的文档提及.有人能给我更自信的证据吗?(因为这里的人们一直关注我在开始时使用的可变集,所以我只是把它改成NSCache,这真的不是重点.我在谈论关于在这里创建本地静态指针的线程安全性(不是这个实例)指针指向))

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

为了防止我没有明确描述竞争条件,考虑两个线程同时调用这个API,同时检查这个静态_sharedCache指针是否存在,如果没有,他们将通过他们自己创建一个新的静态指针.那么它可能是NSMutableSet的两个静态指针,即使dispatch_once保证实例初始化只发生过一次,也就是这两个静态指针中的一个,然后在dispatch_once块之后,第一个调用者得到满意的结果,但是第二个调用者只是返回一个零指针;

考虑这种情况,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;
    }
Run Code Online (Sandbox Code Playgroud)

Cal*_*leb 5

您的代码更大的潜在问题是您正在共享一个可变集.正如gnasher所解释的那样,你所展示的用于分配集合的代码dispatch_once()很好,原因在于答案中解释的原因.但是一旦完成,你就可以跨线程共享一个可变集.除非您采取步骤来同步对该集的访问,否则您可能很容易遇到两个或多个线程同时更改该集的问题.防止这类问题的最简单,最可靠的方法是将集合封装在一个类中,该类确保以线程安全的方式访问集合,例如使用串行调度队列.