C 多处理器系统上的关键部分和内存围栏/屏障
我有一个使用关键部分的Windows DLL(C)。一个被多次调用的特定例程在第一次调用时需要执行一些初始化代码,因此我使用了一个关键部分。然而,由于它被调用了很多次,我试图避免每次调用时进入该节的开销。它似乎可以工作,但我想知道,在使用x64操作系统的多处理器(Intel)系统上运行时,考虑到内存屏障/围栏,是否存在缺陷?以下是简化的代码:C 多处理器系统上的关键部分和内存围栏/屏障,c,memory-management,thread-safety,windows,C,Memory Management,Thread Safety,Windows,我有一个使用关键部分的Windows DLL(C)。一个被多次调用的特定例程在第一次调用时需要执行一些初始化代码,因此我使用了一个关键部分。然而,由于它被调用了很多次,我试图避免每次调用时进入该节的开销。它似乎可以工作,但我想知道,在使用x64操作系统的多处理器(Intel)系统上运行时,考虑到内存屏障/围栏,是否存在缺陷?以下是简化的代码: int _isInitialized = FALSE; CRITICAL_SECTION _InitLock = {0}; BOOL APIENTRY
int _isInitialized = FALSE;
CRITICAL_SECTION _InitLock = {0};
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
ARM_SECTION_BEGIN(ul_reason_for_call)
switch (ul_reason_for_call)
{
case (DLL_PROCESS_ATTACH):
InitializeCriticalSection(&_InitLock);
break;
case (DLL_THREAD_ATTACH):
break;
case (DLL_THREAD_DETACH):
break;
case (DLL_PROCESS_DETACH):
DeleteCriticalSection(&_InitLock);
break;
}
return (TRUE);
}
int myproc(parameters...)
{
if (!_isInitialized) // check first time
{
EnterCriticalSection(&_InitLock);
if (_isInitialized) // check it again
{
LeaveCriticalSection(&_InitLock);
goto initialized;
}
... do stuff ...
_isInitialized = TRUE;
LeaveCriticalSection(&_InitLock);
}
initialized:
... do more stuff ...
return(something)
}
。。。考虑到内存屏障/围栏,是否存在缺陷
使用
在第二次测试中,代码显然存在使用初始值的过时值的风险
if (!_isInitialized) {
EnterCriticalSection(&_InitLock);
if (_isInitialized) // Risk
要确保重新读取\u已初始化
,请使用volatile
其他共享数据
在中指定的以外的其他数据已初始化。。。做一些事情…
并在后面的中使用。。。做更多的事情…
code面临同样的问题,因为优化可能会在第一次之前读取其他数据,如果(!\u已初始化)
代码可以使用其他易失性数据。不幸的是,这可能会导致不可接受的性能阻力。备选方案取决于材料中的内容
风格
我会将\初始化为函数的本地
,放下\
,避免转到
int myproc(parameters...) {
static volatile int isInitialized = FALSE;
if (!isInitialized) {
EnterCriticalSection(&_InitLock);
if (!isInitialized) {
// ... do stuff ...
isInitialized = TRUE;
}
LeaveCriticalSection(&_InitLock);
}
// ... do more stuff ...
return(something)
}
考虑替换<代码>…使用一些等效但简单的代码(更多)填充…
,以便其他人可以编译和测试。你是等效简单代码的最佳评判者。你提到了操作系统,但没有提到感兴趣的编译器。建议使用该编译器标记。感谢回复。Re:doing stuff它实际上可以是通常只做一次,但不应该在DLLMain
中做的任何事情,例如其他DLL的LoadLibrary
。我没有意识到编译器会带来不同InitializeCriticalSection
、EnterCriticalSection
和LeaveCriticalSection
都是导入,我认为它们不会受到编译器的影响,在Visual Studio 2008与2010与2017下构建它有什么不同?我有这三个功能。EnterCriticalSection
作为一个Windows功能,应该可以在Windows运行的任何CPU配置上工作。您可能应该将\u isInitialized
声明为volatile,以确保每个线程都看到另一个线程对其值所做的任何更改。感谢您的建议re:volatile。我没有想到。但这不也适用于临界截面结构吗?我在头文件的“关键”部分的定义中没有看到任何特殊属性。如何防止这些内容过时?@NeilWeicherCRITICAL_部分
用声明中所需的任何属性进行修饰。定义CRITICAL\u节、EnterCriticalSection()、LeaveCriticalSection()
的编译器和库会做任何事情来完成任务<代码>易失性
的存在是为了防止高度优化的编译器跳过读取。您正在使用的编译器与其库配合良好。这里的问题是:如何确保编译与您的代码配合良好。VS complier可能会对其库使用您的代码无法访问的功能(或已很好地隐藏这些功能)。在相关说明中,我可以使volatile的大小有任何限制吗?例如,我可以申报这个吗<代码>静态易失性句柄myHandle=NULL代码>。甚至是一组句柄<代码>静态易失性句柄myHandles[16]={0}代码>。如果是这样的话,这会对性能产生影响吗?@NeilWeicher没有限制。当volatile
不需要时,性能会变慢。我对它做了更多的研究。我的误解volatile
不会影响声明,它会影响代码优化。例如,以以下代码为例:static volatile BYTE\u isInitialized=0xFF;而(_i初始化-->0)代码>。如果没有volatile
,编译器可能会使用寄存器来递减变量。使用volatile
时,它将在每个循环中减少内存位置本身。顺便说一句-谢谢所有的好回复。
int myproc(parameters...) {
static volatile int isInitialized = FALSE;
if (!isInitialized) {
EnterCriticalSection(&_InitLock);
if (!isInitialized) {
// ... do stuff ...
isInitialized = TRUE;
}
LeaveCriticalSection(&_InitLock);
}
// ... do more stuff ...
return(something)
}