简体   繁体   English

memory_order_relaxed 如何在智能指针中增加原子引用计数?

[英]How can memory_order_relaxed work for incrementing atomic reference counts in smart pointers?

Consider the following code snippet taken from Herb Sutter's talk on atomics:考虑以下摘自 Herb Sutter 关于原子的演讲的代码片段:

The smart_ptr class contains a pimpl object called control_block_ptr containing the reference count refs . 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 says the increment of refs in Thread A can use memory_order_relaxed because "nobody does anything based on the action". Herb Sutter 说线程 A 中refs的增量可以使用 memory_order_relaxed 因为“没有人根据动作做任何事情”。 Now as I understand memory_order_relaxed, if refs equals N at some point and two threads A and B execute the following code:现在,据我所知,memory_order_relaxed,如果refs在某个时刻等于 N 并且两个线程 A 和 B 执行以下代码:

control_block_ptr->refs.fetch_add(1, memory_order_relaxed);

then it may happen that both threads see the value of refs to be N and both write N+1 back to it.那么可能会发生两个线程都看到refs的值是 N 并且都将 N+1 写回它。 That will clearly not work and memory_order_acq_rel should be used just as with the destructor.这显然不起作用,并且应该像使用析构函数一样使用 memory_order_acq_rel。 Where am I going wrong?我哪里错了?

EDIT1: Consider the following code. 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);

What is the value of refs observed by Thread 2 before the call to fetch_add?在调用 fetch_add 之前,线程 2 观察到的refs值是多少? Could it be either N or N+1?它可能是 N 或 N+1? What is the value of refs observed by Thread 2 after the call to fetch_add?调用 fetch_add 后线程 2 观察到的 refs 值是多少? Must it be at least N+2?必须至少是 N+2 吗?

[Talk 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)] [讨论 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)]

Boost.Atomic library that emulates std::atomic provides similar reference counting example and explanation , and it may help your understanding.模拟std::atomic Boost.Atomic 库提供了类似的引用计数示例和解释,它可能有助于您的理解。

Increasing the reference counter can always be done with memory_order_relaxed : New references to an object can only be formed from an existing reference, and passing an existing reference from one thread to another must already provide any required synchronization.始终可以使用memory_order_relaxed来增加引用计数器:对对象的新引用只能从现有引用形成,并且将现有引用从一个线程传递到另一个线程必须已经提供了任何所需的同步。

It is important to enforce any possible access to the object in one thread (through an existing reference) to happen before deleting the object in a different thread.在另一个线程中删除对象之前,强制执行对一个线程中对象的任何可能访问(通过现有引用),这一点很重要。 This is achieved by a "release" operation after dropping a reference (any access to the object through this reference must obviously happened before), and an "acquire" operation before deleting the object.这是通过删除引用后的“释放”操作(通过此引用对对象的任何访问显然必须发生在之前)和删除对象之前的“获取”操作来实现的。

It would be possible to use memory_order_acq_rel for the fetch_sub operation, but this results in unneeded "acquire" operations when the reference counter does not yet reach zero and may impose a performance penalty.可以将memory_order_acq_rel用于 fetch_sub 操作,但是当引用计数器尚未达到零时,这会导致不需要的“获取”操作,并且可能会造成性能损失。

From C++ reference on std::memory_order :来自std::memory_order上的 C++ 参考:

memory_order_relaxed: Relaxed operation: there are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed memory_order_relaxed:放宽操作:没有对其他读或写施加同步或排序约束,仅保证该操作的原子性

There is also an example below on that page . 在该页面下方还有一个示例。

So basically, std::atomic::fetch_add() is still atomic, even when with std::memory_order_relaxed , therefore concurrent refs.fetch_add(1, std::memory_order_relaxed) from 2 different threads will always increment refs by 2. The point of the memory order is how other non-atomic or std::memory_order_relaxed atomic operations can be reordered around the current atomic operation with memory order specified.所以基本上, std::atomic::fetch_add()仍然是原子的,即使使用std::memory_order_relaxed ,因此来自 2 个不同线程的并发refs.fetch_add(1, std::memory_order_relaxed)总是将refs增加 2。重点内存顺序是其他非原子或std::memory_order_relaxed原子操作如何在指定内存顺序的当前原子操作周围重新排序。

As this is rather confusing (at least to me) I'm going to partially address one point:由于这相当令人困惑(至少对我而言),因此我将部分解决一个问题:

(...) then it may happen that both threads see the value of refs to be N and both write N+1 back to it (...) (...)那么可能会发生两个线程都看到 refs 的值为 N 并且都将 N+1 写回它(...)

According to @AnthonyWilliams in this answer , the above sentence seems to be wrong as:根据@AnthonyWilliams 在这个答案中的说法,上面的句子似乎是错误的:

The only way to guarantee you have the "latest" value is to use a read-modify-write operation such as exchange(), compare_exchange_strong() or fetch_add().保证您拥有“最新”值的唯一方法是使用读取-修改-写入操作,例如 exchange()、compare_exchange_strong() 或 fetch_add()。 Read-modify-write operations have an additional constraint that they always operate on the "latest" value, so a sequence of ai.fetch_add(1) operations by a series of threads will return a sequence of values with no duplicates or gaps.读-修改-写操作有一个额外的约束,它们总是对“最新”值进行操作,因此一系列线程的 ai.fetch_add(1) 操作序列将返回一个没有重复或间隙的值序列。 In the absence of additional constraints, there's still no guarantee which threads will see which values though.在没有额外约束的情况下,仍然无法保证哪些线程会看到哪些值。

So, given the authority argument, I'd say it's impossible that both threads see the value going from N to N+1 .因此,考虑到权限参数,我想说两个线程都不可能看到从NN+1 的值

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM