C++11 原子容器的线程安全

C++11 Thread Safety of Atomic Containers

我正在尝试实现一个没有互斥体的线程安全 STL 向量。所以我遵循 this post 并为原子原语实现了一个包装器。

然而,当我运行下面的代码时,它从下面的代码中显示了 Failed! 两次(只有两个竞争条件实例)所以它似乎不是线程安全的。我想知道我该如何解决这个问题?

包装类

template<typename T>
struct AtomicVariable
{
    std::atomic<T> atomic;

    AtomicVariable() : atomic(T()) {}

    explicit AtomicVariable(T const& v) : atomic(v) {}
    explicit AtomicVariable(std::atomic<T> const& a) : atomic(a.load()) {}

    AtomicVariable(AtomicVariable const&other) : 
        atomic(other.atomic.load()) {}

    inline AtomicVariable& operator=(AtomicVariable const &rhs) {
        atomic.store(rhs.atomic.load());
        return *this;
    }

    inline AtomicVariable& operator+=(AtomicVariable const &rhs) {
        atomic.store(rhs.atomic.load() + atomic.load());
        return *this;
    }

    inline bool operator!=(AtomicVariable const &rhs) {
        return !(atomic.load() == rhs.atomic.load());
    }
};

typedef AtomicVariable<int>    AtomicInt;

函数和测试

// Vector of 100 elements.
vector<AtomicInt> common(100, AtomicInt(0));

void add10(vector<AtomicInt> ¶m){
    for (vector<AtomicInt>::iterator it = param.begin();
        it != param.end(); ++it){
        *it += AtomicInt(10);
    }
}

void add100(vector<AtomicInt> ¶m){
    for (vector<AtomicInt>::iterator it = param.begin();
        it != param.end(); ++it){
        *it += AtomicInt(100);
    }
}

void doParallelProcessing(){

    // Create threads
    std::thread t1(add10, std::ref(common));
    std::thread t2(add100, std::ref(common));

    // Join 'em
    t1.join();
    t2.join();

    // Print vector again
    for (vector<AtomicInt>::iterator it = common.begin();
        it != common.end(); ++it){
        if (*it != AtomicInt(110)){
            cout << "Failed!" << endl;
        }
    }
}


int main(int argc, char *argv[]) {

    // Just for testing purposes
    for (int i = 0; i < 100000; i++){
        // Reset vector
        common.clear();
        common.resize(100, AtomicInt(0));
        doParallelProcessing();
    }
}

有原子容器这种东西吗?我还用常规 vector<int> 测试了它,它没有任何 Failed 输出,但这可能只是巧合。

请注意

           atomic.store(rhs.atomic.load() + atomic.load());

不是原子的

你有两种选择来解决它。 回忆 1) 使用互斥量。

EDIT 正如评论中提到的 T.C 这是无关紧要的,因为这里的操作将是 load() 然后 load() 然后 store() (不是放松模式) - 所以内存顺序在这里不相关。

2) 使用内存顺序http://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/

memory_order_acquire: guarantees that subsequent loads are not moved before the current load or any preceding loads. memory_order_release: preceding stores are not moved past the current store or any subsequent stores.

我仍然不确定 2,但我认为如果商店不并行,它会起作用。

只需将运算符+=写为:

    inline AtomicVariable& operator+=(AtomicVariable const &rhs) {
        atomic += rhs.atomic;
        return *this;
    }

在文档中:http://en.cppreference.com/w/cpp/atomic/atomic 运算符 += 是原子的。

您的示例失败,因为以下执行场景是可能的:

  1. 线程 1 - rhs.atomic.load() - returns 10 ;线程 2 - rhs.atomic.load() - returns 100
  2. 线程 1 - atomic.load() - returns 0 ;线程 2 - atomic.load - returns 0
  3. Thread1 - 添加值 (0 + 10 = 10) ; Thread2 - 添加值 (0 + 100)
  4. 线程 1 - atomic.store(10);线程 2 - atomic.store(100)

最终在这种情况下,原子值可能是 10 或 100,取决于哪个线程首先执行 atomic.store。