【问题标题】:Is pointer address swapping always an atomic operation in C++?指针地址交换是否始终是 C++ 中的原子操作?
【发布时间】:2014-03-29 02:22:08
【问题描述】:

关于这个问题Is there cases where a 32-bit variable could not been properly-aligned 和提供的答案,我可以假设我在 windows 平台下工作时可以交换地址而没有任何副作用吗?

例如:

struct Foo
{
    // whatever Foo can hold
};

struct Bar
{
    void buildFoo()
    {
        auto tmp = new Foo;

        // do some stuff on tmp, or not

        foo = tmp;
    }

    Foo* foo;
};

现在,让一些线程通过Bar 的实例使用foo,而其他线程调用Bar::buildFoo(),会有什么后果?

【问题讨论】:

  • 交换通常不是原子的。它必须读取两个值,并写入两个值。 (所以当交换ab 时,ab 的旧值结束,ba 的旧值结束)
  • jalf 说得对,swapping 通常不是原子的,但是 Windows 确实提供了诸如 InterlockedExchangePointer 之类的方法来执行交换。
  • 请注意,您不是swap,而是分配
  • 还要注意,指针可能是 64 位的。
  • @Jarod42:确实,我不是在交换。谢谢。

标签: c++ windows multithreading


【解决方案1】:

C++ 标准

,不能保证同时修改/访问原始2指针是c++中的原子操作。

C++ 标准规定,如果一个线程修改内存位置而另一个线程修改/访问相同的内存位置,则存在数据竞争,如果存在这样的数据竞争,则程序将遭受 未定义的行为 em>。

[intro.multithread]

4) 两个表达式求值冲突 如果其中一个修改了内存位置 (1.7) 而另一个访问或修改了相同的内存位置。

...

21) 如果一个程序在不同线程中包含两个相互冲突的动作,则该程序的执行包含一个数据竞争,其中至少一个不是原子的,并且两者都不会在另一个之前发生。任何此类数据竞争都会导致未定义的行为。

1.raw 没有被包裹在 std::atomic<Foo*> 或等效项中。


实现特定行为(windows 32/64bit)

在 Windows 下,保证对 正确对齐 32-bit 变量的读/写始终是原子,正如 the article 所述,由 question/answer 链接你之前提到过。

对正确对齐的64-bit 变量的访问在64-bit 窗口上也是原子的。

互锁变量访问 - http://msdn.microsoft.com/en-us/library/ms684122%28VS.85%29.aspx

对正确对齐的 32 位变量的简单读写是原子操作。换句话说,您最终不会只更新变量的一部分;所有位都以原子方式更新。

但是,不能保证访问是同步的。如果两个线程正在读取和写入同一个变量,则无法确定一个线程是否会在另一个线程执行其写入操作之前执行其读取操作。


这是什么意思?

标准说明了一件事,而 microsoft 的文档说明了另一件事。我们应该信任和使用哪些内容?这当然取决于我们在做什么。

如果我们正在为 Windows 平台单独开发,我们可以阅读所使用的编译器在代码生成方面的保证并从那里开始,但是如果我们想编写可以在不同平台下编译和运行的代码,唯一的正确信任的东西是标准。


那么在 Windows 下工作时,我可以安全地交换 32 位变量吗?

如果您的 “交换” 是指操作,例如下面 sn-p 中所写的操作,则答案是 ,但如果您的意思是 ”分配”答案是是的

void swap (int& a, int& b) {
  int tmp = a;
  
  a = b;
  b = tmp;
}

int main () {
  int x = 1;
  int y = 2;

  swap (x, y);
}

上面的 sn-p(或前面提到的文档)中没有任何内容表明这将是一个原子操作,当查看我们的 swap 的实现时,我们很容易看到这些操作没有正确同步。

在 Windows 下读取/写入单个 32 位变量是安全的,但在上面没有任何东西可以保证 xy 在我们处于中间时不具有值 2我们的swap

但是,可以保证x 永远不会包含之前x 中50% 的字节和y 中50% 的字节,或类似的......单个写入是原子的。

【讨论】:

  • 实际上 ms 文档也这么说:虽然读/写可能是原子的(实际上是应该参考的处理器的英特尔规范),但交换(涉及其中的多个)当然是不是。
【解决方案2】:

设置foo 可能是原子的,但它是特定于实现的。原因是大多数平台在一条指令中写入一个字大小的地址,这是一个原子操作。但是,您的平台不要求将其指针存储在一个字大小的位置,尽管很少不这样做。

您还需要考虑指令重新排序以及其他线程可能在一段时间内看不到对foo 的写入的事实,指针的位置可能会保存在处理器缓存中并且不会刷新到内存中。

鉴于所有这些,您最好使用 std::atomic 类型,或者如果您无法访问 C++11,那么应该有某种互锁的读/写函数可用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-07
    • 1970-01-01
    • 2018-06-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多