[英]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
. 因此用pInstance
和temp
命令。 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.