简体   繁体   English

使用volatile实现C ++ 98/03中的双重检查锁定

[英]Implementation of Double Checked Locking in C++ 98/03 using volatile

Reading this article about Double Checked Locking Pattern in C++, I reached the place (page 10) where the authors demonstrate one of the attempts to implement DCLP "correctly" using volatile variables: 阅读文章关于双重检查在C ++锁定模式,我到达了作者展示了以“正确”贯彻DCLP使用的尝试之一的位置(第10页) volatile变量:

class Singleton {
public:
  static volatile Singleton* volatile instance();

private:
  static volatile Singleton* volatile pInstance;
};

// from the implementation file
volatile Singleton* volatile Singleton::pInstance = 0;
volatile Singleton* volatile Singleton::instance() {
  if (pInstance == 0) {
    Lock lock;
    if (pInstance == 0) {
      volatile Singleton* volatile temp = new Singleton;
      pInstance = temp;
    }
  }
  return pInstance;
}

After such example there is a text snippet that I don't understand: 在这样的例子之后有一个我不明白的文本片段:

First, the Standard's constraints on observable behavior are only for an abstract machine defined by the Standard, and that abstract machine has no notion of multiple threads of execution. 首先,标准对可观察行为的约束仅适用于标准定义的抽象机器,而抽象机器不具有多个执行线程的概念。 As a result, though the Standard prevents compilers from reordering reads and writes to volatile data within a thread, it imposes no constraints at all on such reorderings across threads. 其结果是,虽然标准防止从编译器重新排序读取和螺纹写入到易失性数据,它规定没有约束在所有上线程这种重排序。 At least that's how most compiler implementers interpret things. 至少这是大多数编译器实现者解释事物的方式。 As a result, in practice, many compilers may generate thread-unsafe code from the source above. 因此,在实践中,许多编译器可能会从上面的源生成线程不安全的代码。

and later: 然后:

... C++'s abstract machine is single-threaded, and C++ compilers may choose to generate thread-unsafe code from source like the above, anyway. ... C ++的抽象机器是单线程的,无论如何,C ++编译器可能会选择从源代码生成线程不安全的代码。

These remarks are related to the execution on the uni-processor, so it's definitely not about cache-coherence issues. 这些评论与单处理器上的执行有关,所以它绝对不是缓存一致性问题。

If the compiler can't reorder reads and writes to volatile data within a thread, how can it reorder reads and writes across threads for this particular example thus generating thread-unsafe code? 如果编译器无法对线程的易失性数据进行读取和写入,那么如何针对此特定示例重新排序线程的读取和写入从而生成线程不安全的代码?

The pointer to the Singleton may be volatile, but the data within the singleton is not. 指向Singleton的指针可能是易失性的,但单例的数据则不是。

Imagine Singleton has int x, y, z; 想象一下Singleton有int x, y, z; as members, set to 15, 16, 17 in the constructor for some reason. 作为成员,由于某种原因在构造函数中设置为15, 16, 17

  volatile Singleton* volatile temp = new Singleton;
  pInstance = temp;

OK, temp is written before pInstance . 好的, temp是在pInstance之前pInstance When are x,y,z written relative to those? x,y,z何时相对于那些? Before? 之前? After? 后? You don't know. 你不知道。 They aren't volatile, so they don't need to be ordered relative to the volatile ordering. 它们不是易失性的,因此不需要相对于易失性排序进行排序。

Now a thread comes in and sees: 现在一个线程进来看到:

if (pInstance == 0) {  // first check

And let's say pInstance has been set, is not null. 让我们说pInstance已经设置,不是空的。 What are the values of x,y,z ? x,y,z的值是多少? Even though new Singleton has been called, and the constructor has "run", you don't know whether the operations that set x,y,z have run or not. 即使已经调用了new Singleton ,并且构造函数已经“运行”,您也不知道设置x,y,z的操作是否已运行。

So now your code goes and reads x,y,z and crashes, because it was really expecting 15,16,17 , not random data. 所以现在你的代码去读取x,y,z和崩溃,因为它真的期待15,16,17 ,而不是随机数据。

Oh wait, pInstance is a volatile pointer to volatile data! 哦等等, pInstance是指向易失性数据的易失性指针! So x,y,z is volatile right? 所以x,y,z是不稳定的吗? Right? 对? And thus ordered with pInstance and temp . 因此用pInstancetemp命令。 Aha! 啊哈!

Almost. 几乎。 Any reads from *pInstance will be volatile, but the construction via new Singleton was not volatile. 来自*pInstance任何读取*pInstance将是易失性的,但是通过new Singleton进行的构造并不易变。 So the initial writes to x,y,z were not ordered. 因此,对x,y,z的初始写入没有被排序。 :-( :-(

So you could , maybe , make the members volatile int x, y, z; 所以,你可以也许 ,让会员volatile int x, y, z; OK. 好。 However... 然而...

C++ now has a memory model, even if it didn't when the article was written. C ++ 现在有一个内存模型,即使在撰写文章时没有。 Under the current rules, volatile does not prevent data races. 根据现行规则, volatile不会阻止数据竞争。 volatile has nothing to do with threads. volatile与线程无关。 The program is UB. 该计划是UB。 Cats and Dogs living together. 猫和狗一起生活。

Also, although this is pushing the limits of the standard (ie it gets vague as to what volatile really means), an all-knowing, all-seeing, full-program-optimizing compiler could look at your uses of volatile and say "no, those volatiles don't actually connect to any IO memory addressses etc, they really aren't observable behaviour, I'm just going to make them non-volatile"... 此外,尽管这推动了标准的极限(即,它对于volatile实际意味着什么变得模糊),一个全知,全视,全程序优化的编译器可以看看你对volatile的使用并说“不” ,那些挥发物实际上并没有连接到任何IO内存地址等,它们确实不是可观察的行为,我只是要让它们变得非易失性“......

I think they're referring to the cache coherency problem discussed in section 6 ("DCLP on Multiprocessor Machines". With a multiprocessor system, the processor/cache hardware may write out the value for pInstance before the values are written out for the allocated Singleton . This can cause a 2nd CPU to see the non-NULL pInstance before it can see the data it points to. 我认为它们指的是第6节(“多处理器机器上的DCLP”)中讨论的高速缓存一致性问题 。对于多处理器系统,处理器/高速缓存硬件可能会在为分配的Singleton写出值之前写出pInstance的值。这可能导致第二个CPU在看到它指向的数据之前看到非NULL的pInstance

This requires a hardware fence instruction to ensure all the memory is updated before other CPUs in the system can see any of it. 这需要硬件围栏指令,以确保在系统中的其他CPU可以看到任何内存之前更新所有内存。

If I'm understanding correctly they are saying that in the context of a single-thread abstract machine the compiler may simply transform: 如果我正确理解他们说在单线程抽象机器的上下文中,编译器可能只是转换:

volatile Singleton* volatile temp = new Singleton;
pInstance = temp;

Into: 成:

pInstance = new Singleton;

Because the observable behavior is unchanged. 因为可观察的行为没有改变。 Then this brings us back to the original problem with double checked locking. 然后,这使我们回到双重检查锁定的原始问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 C ++单例实现,双重检查锁定 - C++ singleton implementation, double-checked locking 双重检查锁定实现 - 使用原子与不使用 - Double checked locking implementation - using atomic vs not using C ++并发编程中的双重检查锁定模式 - double checked locking pattern in c++ concurrent programming 在C ++上进行双重检查锁定:对临时指针是新的,然后将其分配给实例 - Double checked locking on C++: new to a temp pointer, then assign it to instance C++98/03 std::is_constructible 实现 - C++98/03 std::is_constructible implementation 我的Double-Checked Locking Pattern实现是否合适? - Is my Double-Checked Locking Pattern implementation right? 通过双重检查的锁定模式实现call_once实现 - call_once implementation through double checked locking pattern 在c ++中使用带内存屏障的双重检查锁定时正确的方法是什么? - What the correct way when use Double-Checked Locking with memory barrier in c++? 通过 C++ 中的“顺序一致原子”语义实现 DCL(双重检查锁定)线程安全吗? - Is DCL (double-checked locking) thread-safe when implemented by ‘Sequentially Consistent Atomics’ semantics in C++? C ++ 03是C ++标准的新版本还是C ++ 98的技术勘误(TC)? - Is C++03 a new version of the C++ Standard or just a Technical Corrigendum (TC) of C++98?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM