C++ 如何有效地将 std::atomic<> 用于非原始类型?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13885617/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use std::atomic<> effectively for non-primitive types?
提问by kfmfe04
The definitions for std::atomic<>
seem to show its obvious usefulness for primitive or perhaps POD-types.
的定义std::atomic<>
似乎显示了它对原始类型或 POD 类型的明显有用性。
When would you actually use it for classes?
你什么时候会真正在课堂上使用它?
When should you avoidusing it for classes?
什么时候应该避免在课堂上使用它?
采纳答案by bames53
The operations std::atomic
makes available on any trivially copyable type are pretty basic. You can construct and destroy atomic<T>
, you can ask if the type is_lock_free()
, you can load and store copies of T
, and you can exchange values of T
in various ways. If that's sufficient for your purpose then you might be better off doing that than holding an explicit lock.
std::atomic
在任何简单的可复制类型上可用的操作是非常基本的。您可以构造和销毁atomic<T>
,可以询问类型is_lock_free()
,可以加载和存储 的副本T
,还可以T
通过各种方式交换 的值。如果这足以满足您的目的,那么您最好这样做而不是持有显式锁。
If those operations aren't sufficient, if for example you need to atomically perform a sequence operations directly on the value, or if the object is large enough that copying is expensive, then instead you would probably want to hold an explicit lock which you manage to achieve your more complex goals or avoid doing all the copies that using atomic<T>
would involve.
如果这些操作还不够,例如,如果您需要直接在值上原子地执行序列操作,或者如果对象足够大以至于复制成本很高,那么您可能想要持有一个您管理的显式锁以实现更复杂的目标或避免使用atomic<T>
会涉及的所有副本。
// non-POD type that maintains an invariant a==b without any care for
// thread safety.
struct T { int b; }
struct S : private T {
S(int n) : a{n}, b{n} {}
void increment() { a++; b++; }
private:
int a;
};
std::atomic<S> a{{5}}; // global variable
// how a thread might update the global variable without losing any
// other thread's updates.
S s = a.load();
S new_s;
do {
new_s = s;
new_s.increment(); // whatever modifications you want
} while (!a.compare_exchange_strong(s, new_s));
As you can see, this basically gets a copy of the value, modifies the copy, then tries to copy the modified value back, repeating as necessary. The modifications you make to the copy can be as complex as you like, not simply limited to single member functions.
如您所见,这基本上是获取值的副本,修改副本,然后尝试将修改后的值复制回来,必要时重复。您对副本所做的修改可以随心所欲地进行,而不仅限于单个成员函数。
回答by Pete Becker
It works for primitive and POD types. The type must be memcpy
-able, so more general classes are out.
它适用于原始类型和 POD 类型。类型必须是memcpy
-able,所以更通用的类已经出来了。
回答by Johan Lundberg
The standard say that
标准说
Specializations and instantiations of the atomic template shall have a deleted copy constructor, a deleted copy assignment operator, and a constexpr value constructor.
原子模板的特化和实例化应具有删除的复制构造函数、删除的复制赋值运算符和 constexpr 值构造函数。
If that is strictly the same as the answer by Pete Becker, I'm not sure. I interpret this such that you are free to specialize on your own class (not only memcpy-able classes).
如果这与皮特贝克尔的答案完全相同,我不确定。我的解释是这样你就可以自由地专注于你自己的类(不仅仅是 memcpy-able 类)。
回答by pocketbroadcast
I'd prefer std::mutex for this kind of scenarios. Nevertheless I've tried a poor mans benchmark to profile a version with std::atomics and std::mutex in a single threaded (and thus perfectly sync) environment.
对于这种情况,我更喜欢 std::mutex 。尽管如此,我已经尝试了一个穷人的基准测试,在单线程(因此完全同步)环境中使用 std::atomics 和 std::mutex 来分析一个版本。
#include <chrono>
#include <atomic>
#include <mutex>
std::mutex _mux;
int i = 0;
int j = 0;
void a() {
std::lock_guard<std::mutex> lock(_mux);
i++;
j++;
}
struct S {
int k = 0;
int l = 0;
void doSomething() {
k++;
l++;
}
};
std::atomic<S> s;
void b() {
S tmp = s.load();
S new_s;
do {
new_s = tmp;
//new_s.doSomething(); // whatever modifications you want
new_s.k++;
new_s.l++;
} while (!s.compare_exchange_strong(tmp, new_s));
}
void main(void) {
std::chrono::high_resolution_clock clock;
auto t1 = clock.now();
for (int cnt = 0; cnt < 1000000; cnt++)
a();
auto diff1 = clock.now() - t1;
auto t2 = clock.now();
for (int cnt = 0; cnt < 1000000; cnt++)
b();
auto diff2 = clock.now() - t2;
auto total = diff1.count() + diff2.count();
auto frac1 = (double)diff1.count() / total;
auto frac2 = (double)diff2.count() / total;
}
on my system the version using std::mutex was faster than the std::atomic approach. I think this is caused by the additional copying of the values. Further, if used in a multithreaded environment, the the busy looping can affect performance too.
在我的系统上,使用 std::mutex 的版本比 std::atomic 方法快。我认为这是由值的额外复制引起的。此外,如果在多线程环境中使用,忙循环也会影响性能。
Summing up, yes it is possible to use std::atomic with various pod types, but in most cases std::mutex is the weapon of choice, as it is intentionally easier to understand what is going on, and therefore is not as prone to bugs as the version presented with the std::atomic.
总而言之,是的,可以将 std::atomic 与各种 pod 类型一起使用,但在大多数情况下,std::mutex 是首选武器,因为它有意更容易理解正在发生的事情,因此不那么容易将错误作为与 std::atomic 一起呈现的版本。
回答by Den-Jason
With Visual Studio 2017 I have experienced compiler error C2338 due to "alignment" issues when attempting to use std::atomic with a class; you're much better off using a std::mutex which I ended up doing anyway.
在 Visual Studio 2017 中,由于“对齐”问题,我在尝试将 std::atomic 与类一起使用时遇到了编译器错误 C2338;你最好使用 std::mutex ,我最终还是这样做了。