【发布时间】:2019-06-25 12:03:33
【问题描述】:
考虑std::atomic<int> x(0)。如果我理解正确,std::memory_order_relaxed 仅保证操作以原子方式发生,但不保证同步。因此,来自 2 个线程的 x.fetch_add(1, std::memory_order_relaxed) 1000 次将始终得到 2000 的最终结果。但是,这些调用中的任何一个的返回值都不能保证反映真实的当前值(例如,第 2000 个增量可能会返回 1700 作为之前的值)。
但是 - 这是我的困惑 - 鉴于这些增量是并行发生的,x.load(std::memory_order_acquire) 会返回什么?还是x.fetch_add(1, std::memory_order_acq_rel)?这些是否返回真实的当前值,或者它们是否存在与由于放松增量而放松排序相同的过期答案问题?
据我所知,该标准仅保证释放到获取(在同一变量上)同步,从而提供真实的当前值。那么如何轻松地与典型的获取-释放语义相结合呢?
例如,我听说std::shared_ptr 的引用计数以宽松的顺序递增并以 acq_rel 的顺序递减,因为它需要确保它具有真实值才能仅删除对象一次。因此,我很想他们会给出真实的当前值,但我似乎找不到任何标准来支持它。
【问题讨论】:
-
我很漂亮,但不完全确定(因此评论而不是回答),轻松同步有问题的变量,因此您将始终拥有正确的值。它只是不能用于同步调用周围的任何else。对于
shared_ptr,您可以放宽增量(因为您在增量之前或之后什么都不做),但是您必须使用 acq_rel 语义减量,因为在释放到非零时,您必须释放对它指向的对象所做的任何写入,如果您是您还必须获取最终的减量器(为零),以便在运行析构函数/释放内存之前获得其他线程写入。 -
了解内存模型的最简单方法是: 1. 假设代码按原样执行(即忽略重新排序); 2. 当您的代码修改内存时,假设其他线程将在可预测的时间以不可预测的顺序观察修改——这是
relaxed模型。现在阅读其他模型——它们只是为其他线程的观察(您的更改)顺序提供额外的保证。 -
嗯...我在这里可能不完全正确:)。检查this discussion 和this
-
@C.M.对,看到我得到了放松和其他人之间的区别,但我似乎在标准中找不到任何东西,使得在同一个变量上混合放松/非放松根据非放松看到的定义明确当前值。像 - 如果在其他线程中更新了宽松存储,使用获取读取仍然会看到当前值吗?
-
@CruzJean 我的看法:atomic
fetch_add对可见性顺序施加了额外的隐含要求——保证始终观察最新值并以原子方式更新它(或者换句话说,在IA-32 甚至宽松的 fetch_add 都会使用 LOCK 前缀)。所以,是的——你可以同时使用它(用于递增和递减),但递减的问题是最后一个--ref紧接着是 dtor 调用,并且该调用必须以递减的方式正确排序(或者它可能发生在之前) -- 因此要求对最终引用计数减量使用更严格的排序
标签: c++ multithreading synchronization atomic stdatomic