我遇到过 C++03 中一些采用这种形式的代码:
struct Foo {
int a;
int b;
CRITICAL_SECTION cs;
}
// DoFoo::Foo foo_;
void DoFoo::Foolish()
{
if( foo_.a == 4 )
{
PerformSomeTask();
EnterCriticalSection(&foo_.cs);
foo_.b = 7;
LeaveCriticalSection(&foo_.cs);
}
}
foo_.a
的读取是否需要保护?例如:
void DoFoo::Foolish()
{
EnterCriticalSection(&foo_.cs);
int a = foo_.a;
LeaveCriticalSection(&foo_.cs);
if( a == 4 )
{
PerformSomeTask();
EnterCriticalSection(&foo_.cs);
foo_.b = 7;
LeaveCriticalSection(&foo_.cs);
}
}
如果是,为什么?
请假设整数是 32 位对齐的。平台是 ARM。
最佳答案
技术上是的,但在许多平台上都不是。首先,让我们假设 int
是 32 位(这很常见,但不是通用的)。
32 位 int
的两个字(16 位部分)可能会被单独读取或写入。在某些系统上,如果 int
未正确对齐,它们将被单独读取。
想象一个系统,您只能进行 32 位对齐的 32 位读取和写入(以及 16 位对齐的 16 位读取和写入),以及一个跨越这种边界的 int
。 int
最初为零(即 0x00000000
)
一个线程将 0xBAADF00D
写入 int
,另一个“同时”读取它。
写入线程首先将0xBAAD
写入int
的高位字。然后读取器线程读取整个 int
(包括高位和低位),得到 0xBAAD0000
—— 这是 int
从未进入的状态故意的!
写入器线程然后写入低位字 0xF00D
。
如前所述,在某些平台上,所有 32 位读/写都是原子的,因此这不是问题。然而,还有其他问题。
大多数锁定/解锁代码都包含对编译器的指令,以防止跨锁重新排序。如果没有防止重新排序,编译器可以自由地重新排序,只要它在单线程上下文中表现得“好像”它会那样工作。因此,如果您在代码中读取 a
,然后读取 b
,编译器可以在读取 a
之前读取 b
,如此长因为它没有看到在该时间间隔内修改 b
的线程内机会。
因此,您正在阅读的代码可能正在使用这些锁来确保变量的读取按照代码中写入的顺序进行。
其他问题在下面的评论中提出,但我觉得没有能力解决它们:缓存问题和可见性。
关于c++ - 整数读取需要临界区保护吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13650829/