整数读取需要保护关键部分吗?

Pau*_*ulH 9 c++ concurrency winapi critical-section

我遇到过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);
    }
}
Run Code Online (Sandbox Code Playgroud)

读取是否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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果是这样,为什么?

请假设整数是32位对齐的.该平台是ARM.

Yak*_*ont 11

技术上是,但在许多平台上没有.首先,让我们假设它int是32位(这是非常常见的,但几乎不是通用的).

32位的两个字(16位部分)int可能会被单独读取或写入.在某些系统上,如果int未正确对齐,它们将单独读取.

想象一个系统,你只能进行32位对齐的32位读写(以及16位对齐的16位读写),并int跨越这样的边界.最初int为零(即0x00000000)

一个线程写入0xBAADF00D,另一个线程int"同时" 写入.

写线程首先写入0xBAAD高位字int.读者线程然后读取整个int(高和低)获取0xBAAD0000- 这是一个int永远不会故意投入的状态!

编写器线程然后写低字0xF00D.

如上所述,在某些平台上,所有32位读/写都是原子的,因此这不是一个问题.然而,还有其他问题.

大多数锁定/解锁代码包括编译器的指令,以防止跨锁重新排序.如果没有这种重新排序的预防,编译器可以自由地重新排序,只要它在单个线程上下文中"as-if"行为就可以这样工作.所以,如果你读a那么b在代码中,编译器可以读取b它读取之前a,只要它没有看到一个在线程机会b在该区间进行修改.

因此,您正在阅读的代码可能正在使用这些锁来确保变量的读取按照代码中编写的顺序进行.

其他问题在下面的评论中提出,但我觉得无法解决这些问题:缓存问题和可见性.

  • 你忘了提到缓存效果. (3认同)
  • @dmaij问题是如果变量被提升为寄存器,它就会崩溃.此外,如果你不通过原子或关键部分强制内存屏障,现代处理器的宽松内存模型可以做奇怪的事情. (2认同)
  • 原子是不够的.您还必须担心可见性和编译器重新排序. (2认同)