【问题标题】:Is this the correct way to atomically read and write a bool?这是原子读写布尔的正确方法吗?
【发布时间】:2011-12-16 19:00:56
【问题描述】:

一个布尔标志由两个线程切换。下面的代码有意义吗?

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);
}

注意几点:

  • 我正在传递一个布尔引用。有意义吗?

  • 为了踢球,我还宣布它是易变的。

  • 函数是静态的

更新:

我想问的基本问题是: 原子性和内存屏障有什么区别?如果线程 A 正在对变量 foo 执行 atomic builtin,那么线程 B 不能对变量 foo 执行任何操作;因此创建了内存屏障?

【问题讨论】:

  • 声明一个函数参数volatile没有多大意义,因为它已经通过在堆栈上分配而被限制在一个线程中。
  • 以上评论有误。 var 不在堆栈中...
  • @AdamZalcman 这是对volatile 的引用。但这可能没有必要。如果这两个__sync 函数按预期工作,那么应该没有任何问题,volatile 或没有。如果他们不这样做,volatile 可能无济于事。
  • 我看不到 bool 变量的声明位置,它可能是在堆上分配的。并且线程堆栈上的局部变量绝不是“受限”的,只要您获取地址或将其作为引用传递给另一个线程,它可能会很好地从“外部”更改。但我同意很可能没有必要声明它volatile,只要您至少使用__sync_* 函数来访问它。

标签: c++ multithreading atomic


【解决方案1】:

对于 read-modify-write 系列操作,您只需要原子操作。隔离读写已经是原子的。

您的问题说两个线程“切换”相同的布尔值。这不是你发布的函数所做的——如果你结合这些函数来执行切换,它仍然不是线程安全的。

为什么不使用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);

您的函数添加了 no 线程安全性。你可以写一个函数

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

例如,基于比较和交换或测试和设置。对于更复杂的情况,“两个线程都读取和写入 same bool,您需要读取器和写入器在某些关键部分协作同步(或查看无锁和无等待算法)。

【讨论】:

  • 有几件事我不明白: * 你的意思是“你只需要原子的读-修改-写动作系列。孤立的读写已经是原子的”。你说的这些 read-modify-write 家族是什么?说“隔离读写已经是原子的”不是多余的吗?这些内置的保证。 * 更重要的是,你所说的“如果你结合这些函数来执行切换,它仍然不是线程安全的”是什么意思。也许“切换”是错误的词,但两个线程都读取和写入 same 布尔值。
【解决方案2】:

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

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

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

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

而且...这真的取决于。例如,如果您只想在一个线程中设置一个标志,并且只想查看它是否在另一个线程中设置,并且在实现这一点之前是否可能需要几微秒也没关系,您可以分配变量不考虑任何原子性。

【讨论】:

  • 感谢 __sync_lock_test_and_set();我曾经考虑过使用它,但我不记得为什么我没有选择它。我正在处理一个大型代码库,我还不能使用 C++0x。
【解决方案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 不提供简单的加载/存储特殊原子操作的原因:它们本来就应该是原子的。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-02
  • 1970-01-01
  • 2014-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-02
相关资源
最近更新 更多