如何使用std :: atomic<>有效的非原始类型?

时间:2012-12-14 20:14:42

标签: c++ multithreading c++11 thread-safety

The definitions for std::atomic<>似乎表明它对原始或POD类型有明显的用处。

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

您应该何时避免将其用于课程?

5 个答案:

答案 0 :(得分:24)

在任何简单的可复制类型上提供的操作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));

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

答案 1 :(得分:12)

适用于原始和POD类型。类型必须是memcpy - 能够,所以更常见的类出来了。

答案 2 :(得分:7)

标准说

  

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

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

答案 3 :(得分:1)

对于这种情况,我更喜欢std :: mutex。尽管如此,我已经尝试了一个糟糕的mans基准来在一个线程(因而完全同步)环境中使用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提供的版本那样容易出错。

答案 4 :(得分:-1)

使用Visual Studio 2017,由于&#34; alignment&#34;我遇到了编译器错误C2338。尝试在类中使用std :: atomic时出现问题;你使用std :: mutex要好得多,我最后还是做了。