在Windows 7平台上的C / Visual Studio下,我遇到一个奇怪的情况。有时会出现问题,我花了很多时间来找到它。问题出在第三方库中,对此我拥有完整的代码。在那里创建了一个线程(printLog语句来自我自己):
...
plafParams->eventThreadFlag = 2;
printLog("before CreateThread");
if (plafParams->hReadThread_p = CreateThread(NULL, 0, ( LPTHREAD_START_ROUTINE ) plafPortReadThread, ( void * ) dlmsInstance, 0,
&plafParams->portReadThreadID) )
{
printLog("after CreateThread: OK");
plafParams->eventThreadFlag = 3;
}
else
{
unsigned int lasterr = GetLastError();
printLog("error CreateThread, last error:%x", lasterr);
/* Could not create the read thread. */
...
...
return FAILURE;
}
printLog("SUCCESS");
...
...
线程函数是:
void *plafPortReadThread(DLMS_GLOBALS *dlmsInstance)
{
PLAF_PARAMS *plafParams;
plafParams = (PLAF_PARAMS *)(dlmsInstance->plafParams);
printLog("start - plafPortReadThread, plafParams->eventThreadFlag=%x", plafParams->eventThreadFlag);
while ((plafParams->eventThreadFlag != 1) && (plafParams->eventThreadFlag != 3))
{
if (plafParams->eventThreadFlag == 0)
{
printLog("exit 1 - plafPortReadThread, plafParams->eventThreadFlag=%x", plafParams->eventThreadFlag);
CloseHandle(plafParams->hReadThread_p);
plafFree((void **)&plafParams);
ExitThread(0);
break;
}
}
printLog("start - plafPortReadThread, proceed=%d", proceed);
...
现在,当在线程内启动while循环之前设置了标志时,一切正常:
SUCCESS
start - plafPortReadThread, plafParams->eventThreadFlag=3
但是有时候线程足够快,因此在实际在外部设置标志之前就启动了while循环。
则输出为:
start - plafPortReadThread, plafParams->eventThreadFlag=2
SUCCESS
最令人惊讶的是,即使将标志设置为3,while循环也不会退出。
似乎,编译器“优化”了该标志并假定它不能从外部更改。
可能是什么问题?我真的很惊讶还是我完全监督了其他事情?我知道,代码不是很优雅,最好用信号量或信号来完成这些事情。但这不是我的代码,我希望更改尽可能少。
除去所有while条件后,它会按预期工作。
我应该将结构或其字段更改为volatile
吗?所有人都说volatile
在我们这个时代是没有用的,不再需要了,除非外围设备改变了存储位置...
答案 0 :(得分:2)
在C11之前,这完全依赖于平台,因为您观察到的效果是由于平台所使用的内存模型所致。这与编译器优化不同,因为线程之间的同步点要求编译器插入屏障指令,而不是例如使某些常量成为常数。对于C11,第7.17.3节指定不同的模型。因此,您的值不会被静态优化,线程A永远不会读取线程B写入的值,但仍具有其本地值。
实际上,许多项目还没有使用C11,因此您可能必须检查平台的文档。请注意,在许多情况下,您不必修改标志的变量类型(以防万一)。大多数内存模型都指定了同步点,这些同步点还禁止对某些指令进行重新排序,例如:
int x = 3;
_Atomic int a = 1;
x = 5;
a = 2;
当x
的值为1时,编译器通常必须确保a
的值为3,并且为a
分配的值为2,x
将具有值5。volatile
不参与这种关系(在C / C ++ 11模型中-经常会混淆,因为它确实参与了Java的事前活动),并且几乎没有用,除非您的写信永远不会进行了优化,因为它们具有副作用,例如LED闪烁,编译器无法理解:
volatile int x = 1; // some special location - blink then clear
x = 1; // blink then clear
x = 1; // blink then clear