这是原子地读取和写入bool的正确方法吗?

时间:2011-12-15 09:39:37

标签: c++ multithreading atomic

布尔标志由两个线程切换。以下代码是否有意义?

static bool ATOMIC_BOOL_READ( volatile bool& var )
{
    return __sync_fetch_and_or(&var, 0);
}

static void ATOMIC_BOOL_WRITE(volatile bool& var, bool newval )
{
    __sync_bool_compare_and_swap( &var, !newval, newval);
}

请注意以下几点:

  • 我正在传递一个bool引用。有道理吗?

  • 为了好事,我也宣称它不稳定。

  • 功能是静态的

更新

我想问的基本问题是: 什么是原子性和记忆障碍之间的区别?如果线程A在变量foo上执行 atomic builtin ,则线程B不能对变量foo执行任何操作;因此造成了记忆障碍?

3 个答案:

答案 0 :(得分:5)

您只需要原子用于读取 - 修改 - 写入操作系列。孤立的读取和写入已经是原子的。

你的问题是两个线程“切换”同一个bool。这不是你发布的函数所做的 - 如果你将这些函数组合起来执行切换,那么仍然不是线程安全的。

为什么不使用std::atomic_int

i=0;是线程安全的,i=i+1;不是因为如果另一个线程同时执行相同的操作,i可能最终只会增加一次而不是两次。这是一个读 - 修改 - 写,一个示例问题序列是(read1,read2,modify1,write1,modify2,write2),用于线程1和2.到目前为止,这是标准的。

现在你能看出为什么这也不是线程安全的吗?

bool x = ATOMIC_BOOL_READ (&b);
x = !x;
ATOMIC_BOOL_WRITE (&b, x);

您的功能无论如何都会添加线程安全。你可以写一个函数

bool atomic_toggle_and_return_new_value (bool * b) { ... }

基于比较和交换或测试和设置,比方说。对于更复杂的情况,“两个线程都读取和写入相同的 bool,您需要读者和编写者在某些关键部分协同同步(或者查看无锁和无等待算法)。” / p>

答案 1 :(得分:4)

__sync_bool_compare_and_swap是正确的,但可能比必要的贵得多。

这取决于你的需要。 __sync_lock_test_and_set会更便宜(并且保证是原子的),但是如果操作是“成功的”,它就不会报告,因为价值是预期的(无论如何它总是“成功”,你确实得到了价值回来了,如果它不是你说的那样,它就不会失败。但是,这是一些并不总是有趣的信息。

如果您在C ++ 0x模式下编译,而不是原子内置,则可以使用std::atomic<bool>,该模式提供.load().store()。这些函数可能更有效(无论是利用某些操作是原子的知识,还是插入障碍,或使用特殊操作,或其他什么),并且您的代码更易于移植(并且更加明显)。

此外,在几乎所有的架构上,你都可以期待(认为无法保证!)无论如何写入bool是原子的。

而且......这实际上取决于。例如,如果您只想在一个线程中设置一个标志,并且只想查看它是否在另一个线程中设置并且在实现它之前它可能需要几微秒并不重要,您可以只分配变量无论任何原子性。

答案 2 :(得分:3)

Atomics本质上是非便携式,这些是GCC扩展,可能在将来不再存在,并且不适用于其他编译器。

只有在完全理解上述陈述后,才应阅读其余答案。

一个值得注意的事实是,现有的所有机器始终保证对特定大小的数据的访问是原子的。这来自于内存和系统总线中的数据以某种粒度传输的基本概念。在大多数机器中,布尔值应该绝对是原子的,所以:

bool ATOMIC_BOOL_READ(volatile bool* b) {
    bool v = *b;
    __sync_synchronize(); // ensure value pushed to memory
    return v;
}

void ATOMIC_BOOL_WRITE(volatile bool* b, bool v) {
    __sync_synchronize(); // read will return fresh value
   *b = v;
}

这可能就是为什么GCC不提供简单的加载/存储特殊原子操作的原因:它们已经被认为是原子的。