我必须明确调用原子加载/存储吗?

88

C++11引入了std::atomic<>模板库。标准规定了store()load()操作,用于原子地设置/获取由多个线程共享的变量。

我的问题是赋值和访问操作是否也是原子的?

也就是说:

std::atomic<bool> stop(false);
...
void thread_1_run_until_stopped()
{
    if(!stop.load())
        /* do stuff */
}

void thread_2_set_stop()
{        
    stop.store(true);
}

等同于:

void thread_1_run_until_stopped()
{
    if(!stop)
        /* do stuff */
}

void thread_2_set_stop()
{        
    stop = true;
}

“stop.load(std::memory_order_relaxed)”和“stop.store(true, std::memory_order_relaxed);”在这里应该没问题,正如Serge所说。你只需要让存储操作及时被看到,而“relaxed”仍然可以保证这一点。只有当你需要同步其他数据时才需要更强的排序。 - Peter Cordes
3个回答

69
非引用类型的赋值和访问操作也是原子的吗?
是的,它们是原子的。atomic::operator T和atomic::operator=等同于atomic::load和atomic::store。所有运算符都在原子类中实现,以便按预期使用原子操作。
我不确定你所说的“非引用”类型是什么意思?不确定引用类型在这里的相关性。

16
相当于没有一致性控制参数的加载和存储版本,也就是说。 - Ben Voigt
1
@user1131467 - 谢谢。已经删除了“reference”,好的,参考资料;-) - bavaza
3
@BenVoigt,它的意思只是使用最严格的内存顺序:std::memory_order_seq_cst。这可以防止自己踩到自己的后脚跟,但可能是一种过早的悲观优化。 - Kuba hasn't forgotten Monica

41
你可以两种方法都使用,但是使用load()/store()的优势在于它们允许指定内存序。这对于性能来说有时很重要,因为你可以指定std::memory_order_relaxed,而atomic<T>::operator Tatomic<T>::operator=则会使用最安全且最慢的std::memory_order_seq_cst。有时候也很重要确保代码正确和易读:虽然默认值std::memory_order_seq_cst是最安全的,因此最有可能是正确的,但读者并不一定立即清楚你正在执行哪种操作(acquire/release/consume),或者你是否进行了此操作(回答:这里是否使用松散顺序就足够了?)。

0
是的,它们是等价的。标准在[atomics.ref.ops]中提到:
T operator=(T desired) const noexcept;

Effects: Equivalent to:

store(desired);
return desired;

并且

T load(memory_order order = memory_order::seq_cst) const noexcept;

Effects: Equivalent to: return load();

请注意,由于转换函数和=都使用std::memory_order::seq_cst,这是最安全的内存顺序,但由于需要额外的同步,所以不是最快的。
为什么我们需要两者呢?
这在一定程度上是为了与C原子操作兼容。C11添加了_Atomic限定符,因此您可以编写以下代码:
_Atomic(int) x;
x = 0;

现在在C++中,完全相同的代码也可以工作。 只有在你关心性能原因下的内存顺序时,才需要显式的loadstore
然而,使用loadstore并保持内存顺序的一致性是一个好的实践。这样,你可以始终表达你打算使用的内存顺序。操作符重载无法表达这个意图。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接