这是自动读写bool类型的正确方法吗?

Is this the correct way to atomically read and write a bool?

本文关键字:方法 类型 读写 bool      更新时间:2023-10-16

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

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执行原子内置,那么线程B不能对变量foo做任何事情;因此制造了一个内存障碍?

只读-修改-写操作族只需要原子。隔离的读和写已经是原子的了。

你的问题说两个线程"切换"相同的bool。这不是你发布的函数所做的——如果你把这些函数组合起来执行切换,它仍然不是线程安全的。

为什么不使用std::atomic_int ?

i=0;是线程安全的,i=i+1;不是,因为如果另一个线程在同一时间做同样的事情,i可能最终只增加一次而不是两次。这是一个读-修改-写,一个示例问题序列是线程1和线程2的(read1,read2,modify1,write1,modify2,write2)。到目前为止,一切都很标准。

现在你可以看到为什么这也不是线程安全的吗?

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

你的函数增加了没有线程安全。你可以写一个函数

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

基于比较-交换或测试-设置。对于更复杂的情况,即"两个线程都读写相同的 bool",则需要读写器在某些临界区上协作同步(或者查看无锁和无等待算法)。

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

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

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

此外,在几乎所有的体系结构中,您还可以期望(尽管不能保证!)写bool是原子的。

…这要看情况。例如,如果你只想在一个线程中设置一个标志,并且只想看看它是否在另一个线程中设置,并且在实现之前可能需要几微秒并不重要,那么你可以直接分配变量,而不考虑任何原子性。

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不提供简单的加载/存储特殊原子操作的原因:它们已经被认为是原子的。