T getValue(){
if (!m_pValue){
EnterCriticalSection(&m_crst);
if (! m_pValue){
T pValue = m_pFactory();
_WriteBarrier();
m_pValue = pValue;
}
LeaveCriticalSection(&m_crst);
}
_ReadBarrier();
return m_pValue;
}
正如作者所述:
在实例化对象之后,在将指针写入m_pValue字段之前,会发现一个_WriteBarrier。这是为了确保在对象的初始化中的写操作永远不会延迟到对m_pValue本身的写操作之后。
由于_WriteBarrier是编译屏障,如果编译器知道LeaveCriticalSection的语义,我认为它并没有什么用处。编译器可能会省略写入pValue的过程,但永远不会进行这样的优化:将赋值操作移动到函数调用之前,否则就会违反程序语义。我相信LeaveCriticalSection有隐式硬件栅栏。因此,在分配给m_pValue之前进行任何写入都将被同步。
另一方面,如果编译器不知道LeaveCriticalSection的语义,那么在所有平台上都需要_WriteBarrier,以防止编译器将赋值操作移出临界区。
至于_ReadBarrier,作者说:类似地,在返回m_value之前,我们需要一个_ReadBarrier,以便在调用getValue之后不会重新排序加载操作。
首先,如果此函数包含在库中,且没有源代码可用,那么编译器如何知道是否存在编译屏障?
其次,如果需要,它将被放置在错误的位置。我认为我们需要在EnterCriticalSection之后正确放置它以表示获取栅栏。与我上面写的类似,这取决于编译器是否理解EnterCriticalSection的语义。
作者还说:
然而,我也要指出,在X86、Intel64和AMD64处理器上都不需要任何栅栏。遗憾的是,像IA64这样的弱处理器使事情变得复杂了。
正如我上面分析的,如果我们在某些平台上需要这些屏障,那么我们在所有平台上都需要它们,因为这些屏障是编译屏障,只是确保编译器可以进行正确的优化,以防它们不理解某些函数的语义。
请纠正我如果我错了。
另一个问题是,是否有参考资料可以指出msvc和gcc了解哪些函数的同步语义?
更新 1:根据答案(m_pValue将在临界区外被访问),并从这里运行示例代码,我认为:
- 我认为作者在这里所指的是硬件屏障而不是编译屏障,请参见来自MSDN的以下引用。
- 我相信硬件屏障也具有隐式的编译屏障(禁用编译优化),但反之则不然(参见这里,使用CPU屏障不会看到任何重新排序,但反之则不然)
作为作者所写:“在X86 Intel64和AMD64处理器上都不需要栅栏”,这是因为这些平台只允许存储-加载重排。屏障不是屏障。应该注意的是,屏障会影响缓存中的所有内容。屏障只影响单个缓存行。
除非绝对必要,否则不应添加屏障。要使用屏障,可以选择一个_Interlocked内置函数之一。
仍然有一个问题,编译器是否理解对进入/离开临界区的调用语义?如果不理解,则可能像以下答案一样进行优化,从而导致不良行为。
谢谢。