![](/img/trans.png)
[英]fetch_sub with memory_order_relaxed for atomic reference counting?
[英]How can memory_order_relaxed work for incrementing atomic reference counts in smart pointers?
考虑以下摘自 Herb Sutter 关于原子的演讲的代码片段:
smart_ptr 类包含一个名为 control_block_ptr 的 pimpl 对象,其中包含引用计数refs 。
// Thread A:
// smart_ptr copy ctor
smart_ptr(const smart_ptr& other) {
...
control_block_ptr = other->control_block_ptr;
control_block_ptr->refs.fetch_add(1, memory_order_relaxed);
...
}
// Thread D:
// smart_ptr destructor
~smart_ptr() {
if (control_block_ptr->refs.fetch_sub(1, memory_order_acq_rel) == 1) {
delete control_block_ptr;
}
}
Herb Sutter 说线程 A 中refs的增量可以使用 memory_order_relaxed 因为“没有人根据动作做任何事情”。 现在,据我所知,memory_order_relaxed,如果refs在某个时刻等于 N 并且两个线程 A 和 B 执行以下代码:
control_block_ptr->refs.fetch_add(1, memory_order_relaxed);
那么可能会发生两个线程都看到refs的值是 N 并且都将 N+1 写回它。 这显然不起作用,并且应该像使用析构函数一样使用 memory_order_acq_rel。 我哪里错了?
EDIT1:考虑以下代码。
atomic_int refs = N; // at time t0.
// [Thread 1]
refs.fetch_add(1, memory_order_relaxed); // at time t1.
// [Thread 2]
n = refs.load(memory_order_relaxed); // starting at time t2 > t1
refs.fetch_add(1, memory_order_relaxed);
n = refs.load(memory_order_relaxed);
在调用 fetch_add 之前,线程 2 观察到的refs值是多少? 它可能是 N 或 N+1? 调用 fetch_add 后线程 2 观察到的 refs 值是多少? 必须至少是 N+2 吗?
[讨论 URL:C++ & Beyond 2012 - http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-2-of-2 (@ 1: 20:00)]
模拟std::atomic
Boost.Atomic 库提供了类似的引用计数示例和解释,它可能有助于您的理解。
始终可以使用
memory_order_relaxed
来增加引用计数器:对对象的新引用只能从现有引用形成,并且将现有引用从一个线程传递到另一个线程必须已经提供了任何所需的同步。在另一个线程中删除对象之前,强制执行对一个线程中对象的任何可能访问(通过现有引用),这一点很重要。 这是通过删除引用后的“释放”操作(通过此引用对对象的任何访问显然必须发生在之前)和删除对象之前的“获取”操作来实现的。
可以将
memory_order_acq_rel
用于 fetch_sub 操作,但是当引用计数器尚未达到零时,这会导致不需要的“获取”操作,并且可能会造成性能损失。
来自std::memory_order
上的 C++ 参考:
memory_order_relaxed:放宽操作:没有对其他读或写施加同步或排序约束,仅保证该操作的原子性
在该页面下方还有一个示例。
所以基本上, std::atomic::fetch_add()
仍然是原子的,即使使用std::memory_order_relaxed
,因此来自 2 个不同线程的并发refs.fetch_add(1, std::memory_order_relaxed)
总是将refs
增加 2。重点内存顺序是其他非原子或std::memory_order_relaxed
原子操作如何在指定内存顺序的当前原子操作周围重新排序。
由于这相当令人困惑(至少对我而言),因此我将部分解决一个问题:
(...)那么可能会发生两个线程都看到 refs 的值为 N 并且都将 N+1 写回它(...)
根据@AnthonyWilliams 在这个答案中的说法,上面的句子似乎是错误的:
保证您拥有“最新”值的唯一方法是使用读取-修改-写入操作,例如 exchange()、compare_exchange_strong() 或 fetch_add()。 读-修改-写操作有一个额外的约束,它们总是对“最新”值进行操作,因此一系列线程的 ai.fetch_add(1) 操作序列将返回一个没有重复或间隙的值序列。 在没有额外约束的情况下,仍然无法保证哪些线程会看到哪些值。
因此,考虑到权限参数,我想说两个线程都不可能看到从N到N+1 的值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.