布尔标志由两个线程切换。下面的代码有意义吗?
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 执行任何操作;因此造成了记忆障碍?
你的问题说两个线程“切换”同一个布尔值。这不是你发布的函数所做的——如果你组合这些函数来执行切换,它
仍然不是线程安全的。 为什么不使用
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
这取决于你需要什么。
__sync_lock_test_and_set
会更便宜(并且保证是原子的),但它不会报告操作是否“成功”,因为该值是预期的(无论如何它总是“成功”,并且您确实得到了值)同样,如果不是你所说的那样,它也不会“失败”)。然而,这些信息并不总是有趣。
如果您在 C++0x 模式下编译,则可以使用
std::atomic<bool>
来代替原子内置函数,该模式提供 .load()
和 .store()
。这些函数可能更高效(要么利用某些操作是原子的知识,要么插入障碍,要么使用特殊操作,或其他什么),并且您的代码更可移植(并且更明显)。
此外,在几乎所有架构上,您还可以期望(认为不能保证!)对 bool 的写入无论如何都是原子的。
而且……这确实取决于。例如,如果您只想在一个线程中设置一个标志,并且只想查看它是否在另一个线程中设置,并且在实现这一点之前可能需要多花几微秒也没关系,您可以只分配变量无论任何原子性。
原子本质上是
不可移植的,这些是 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 不提供简单的加载/存储特殊原子操作的原因:它们已经被认为是原子的。