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

时间:2014-09-08 21:01:35

标签: objective-c multithreading race-condition static-variables

由于以下代码是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块之后,第一个调用者得到满意的结果,但是第二个调用者只是返回一个零指针;

考虑这种情况,A和B是两个线程,同时输入这个代码,在这里检查静态指针“_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;
    }

3 个答案:

答案 0 :(得分:5)

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

答案 1 :(得分:4)

这就是dispatch_once的重点。 dispatch_once代码百分之百保证完全执行一次,这就是它被称为dispatch_once的原因。并且百分之百保证第二次和其他呼叫将等到第一次呼叫结束,并且所有内存写入都已完成。

答案 2 :(得分:1)

在与熟悉编译器的人交谈之后,我只是理解与全局静态变量相同,本地静态变量也在main()开始之前初始化。只有它无法被访问,因为没有提及它的参考。所以回到关于这个问题的案例。即使两个线程同时输入这些+(NSCahce *)sharedCache,也只会有一个静态_sharedCache指针实例。这意味着竞争条件在这里无效。