std::atomic
在任何可简单复制的类型上可用的操作都非常基本。你可以构造和销毁atomic<T>
,你可以询问is_lock_free()
的类型,你可以加载和存储T
的副本,你可以通过各种方式交换T
的值。如果这足以满足您的目的,那么您这样做可能比持有显式锁更好。
如果这些操作还不够,例如,如果您需要直接对值原子地执行序列操作,或者如果对象足够大以至于复制成本很高,那么您可能希望持有一个显式锁设法实现更复杂的目标或避免使用
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));
如您所见,这基本上获取值的副本,修改副本,然后尝试将修改后的值复制回来,根据需要重复。您对副本所做的修改可以任意复杂,而不仅仅是限于单个成员函数。
它适用于原始类型和 POD 类型。该类型必须是
memcpy
可用的,因此更通用的类已经出来了。
标准是这么说的
原子模板的特化和实例化应具有 删除的复制构造函数、删除的复制赋值运算符和 constexpr 值构造函数。
如果这与皮特·贝克尔的答案完全相同,我不确定。我对此的解释是,您可以自由地专注于您自己的类(不仅仅是可 memcpy 的类)。
对于这种情况,我更喜欢 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;
}
在我的系统上,使用 std::mutex 的版本比 std::atomic 方法更快。我认为这是由值的额外复制引起的。此外,如果在多线程环境中使用,繁忙循环也会影响性能。
总结一下,是的,可以将 std::atomic 与各种 pod 类型一起使用,但在大多数情况下,std::mutex 是首选武器,因为它有意更容易理解正在发生的事情,因此并不像由于 std::atomic 提供的版本容易出现错误。