原子分配操作员

时间:2013-07-08 12:16:58

标签: c++ winapi atomic assignment-operator

我正在实现一个非常轻的原子包装器作为C ++ for Windows中原始数据类型的学习练习,我有一些关于实现赋值运算符的简单问题。考虑以下两种实现:

// Simple assignment
Atomic& Atomic::operator=(const Atomic& other)
{
    mValue = other.mValue;
    return *this;
}

// Interlocked assignment
Atomic& Atomic::operator=(const Atomic& other)
{
    _InterlockedExchange(&mValue, other.mValue);
    return *this;
}

假设mValue是正确的类型,并且 Atomic 类将其作为成员。

  1. 线程安全赋值运算符需要_InterlockedExchange,还是足以保证线程安全的简单实现?
  2. 如果简单赋值是线程安全的,那么是否需要为此类实现赋值运算符?编译器默认应该足够了,不是吗?
  3. 如果简单赋值在Windows中是线程安全的,那么它在其他平台上是否也是线程安全的?是否需要_InterlockedExchange来保证其他平台上的线程安全?

3 个答案:

答案 0 :(得分:7)

如果 mValue是基本类型(在32位CPU上最多32位宽,在64位CPU上最多64位宽),并且你在x86 CPU上运行(32位或64位模式)你没有手动错位数据,然后内存读/写是保证是原子的。

这本身并不意味着编译器不会重新排序内存访问,甚至不会完全优化它,但CPU确实保证对那些大小的数据进行任何良好对齐的读/写都是原子的。 / p>

但是,请注意我说的是原子性,而不是线程安全,因为“线程安全”取决于使用代码的上下文。

答案 1 :(得分:4)

C ++原子类型处理的三个问题。首先,线程切换可能发生在读或写的中间,导致数据乱码;这被称为“撕裂”。其次,每个处理器都有自己的数据缓存,在一个线程中写入值不一定会更新另一个处理器缓存中的值;这是“陈旧数据”。第三,如果结果不违反各种规则,编译器可以重新排序指令以提高效率;如果你不告诉它在线程之间共享数据,它可能会让你感到惊讶。

使用std::atomic(或各种特定于实现的机制来确保原子性)处理所有三个问题。 没有充分的理由绕过它们;库和编译器编写者几乎肯定比你如何生成有效的代码更好地了解。

答案 2 :(得分:2)

  1. 这取决于mValue的大小和对齐方式,这进一步取决于Atomic对象的对齐方式。通常,如果大小等于CPU寄存器的大小并且正确对齐,则写入是原子的。 (例如:在32位CPU上与32位边界对齐的32位数据类型将以原子方式写入。在这种情况下,无法保证64位数据类型的原子性。)
  2. 正确,编译器默认实现将与您给出的“简单赋值”示例相同,假设mValue是唯一的实例数据成员。
  3. 通常,是的,因为这更像是CPU和架构的功能,而不是特定的操作系统。如果mValue的写入不是原子的,则需要类似的构造。 (例如,请参阅GCC documentation regarding built-in atomic operations。)