布尔标志由两个线程切换。下面的代码有意义吗?
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不提供简单的加载/存储特殊原子操作的原因:它们已经被认为是原子的。