这是原子读写布尔值的正确方法吗?

问题描述 投票:0回答:3

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

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 上执行“原子内置函数”,则线程 B 无法对变量 foo 执行任何操作;因此造成了记忆障碍?

c++ multithreading atomic c++98
3个回答
6
投票

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

仍然

不是线程安全的。 为什么不使用

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

你的函数添加了
no

任何线程安全性。你可以写一个函数 bool atomic_toggle_and_return_new_value (bool * b) { ... }

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

__sync_bool_compare_and_swap

4
投票

这取决于你需要什么。

__sync_lock_test_and_set
会更便宜(并且保证是原子的),但它不会报告操作是否“成功”,因为该值是预期的(无论如何它总是“成功”,并且您确实得到了值)同样,如果不是你所说的那样,它也不会“失败”)。然而,这些信息并不总是有趣。

如果您在 C++0x 模式下编译,则可以使用

std::atomic<bool>
来代替原子内置函数,该模式提供 .load()

.store()

。这些函数可能更高效(要么利用某些操作是原子的知识,要么插入障碍,要么使用特殊操作,或其他什么),并且您的代码更可移植(并且更明显)。


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

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

原子本质上是

不可移植的

,这些是 GCC 扩展,将来可能不再存在,并且无法在其他编译器上运行。


3
投票

一个值得注意的事实是,现有的所有机器始终保证对特定大小的数据的访问是原子的。这来自于内存和系统总线中的数据以一定的

粒度

传输的基本概念。在大多数机器中,布尔值绝对应该是原子的,所以:

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

© www.soinside.com 2019 - 2024. All rights reserved.