如何有效地将std :: atomic <>用于非原始类型?

kfm*_*e04 42 c++ multithreading thread-safety c++11

这些定义std::atomic<>似乎表明其对原始或POD类型的明显有用性.

你什么时候才能将它用于课堂?

什么时候应该避免将它用于课堂?

bam*_*s53 27

这些操作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));
Run Code Online (Sandbox Code Playgroud)

如您所见,这基本上获取值的副本,修改副本,然后尝试复制修改后的值,并根据需要重复.您对副本所做的修改可能非常复杂,而不仅限于单个成员函数.

  • @ kfmfe04:你需要调用a.load()来获取你的S,之后你没有防范并且每个方法调用都没有被保护.所有你得到的是加载/存储到'a'. (3认同)
  • @ kfmfe04每种方法都不像互斥体。例如,您可以调用多个方法并将结果作为单个原子事务应用。发生的情况是,您获得了本地的非共享副本,可以根据需要修改本地副本,然后尝试将修改后的数据复制回共享变量中。 (2认同)
  • 循环_does_加载值:`compare_exchange_strong`要么成功更新原子,要么将'expected'参数中的值替换为新观察到的值,以便您知道下次会发生什么.因此,如果交换失败,则将新值加载到`s`中,然后循环复制新值,再次进行更改,这可能与前一次迭代的结果不同,并尝试存储新值. (2认同)

Pet*_*ker 14

它适用于原始和POD类型.类型必须是memcpy可用的,因此更通用的类已经出来.

  • 所需要的只是该类型可以轻易复制.POD类型比这更严格,因此许多非POD类型可以与`atomic <T>`一起使用. (13认同)

Joh*_*erg 7

标准说

原子模板的特化和实例化应具有已删除的复制构造函数,已删除的复制赋值运算符和constexpr值构造函数.

如果这与Pete Becker的答案完全相同,我不确定.我解释这一点,你可以自由地专注于你自己的课程(不仅仅是memcpy-able课程).

  • 我想你的意思是引用第1段:"有一个泛型类模板`atomic <T>`.模板参数T的类型应该是可以轻易复制的(3.9),"因为第3段,你引用的段落,不是不要说同样的事情,甚至可以指定对泛型类模板`atomic <T>`使用的类型的任何要求. (2认同)
  • 在标准库中实现`atomic`要求`T`是`memcpy`; 这就是`std :: atomic`复制值的方式.这样做的原因是为了避免通过赋值运算符调用用户代码,因为这可能导致死锁. (2认同)