【问题标题】:C++11 Thread Safety of Atomic Containers原子容器的 C++11 线程安全
【发布时间】:2015-01-04 18:14:43
【问题描述】:

我正在尝试实现一个没有互斥锁的线程安全 STL 向量。所以我通过this 发布并为原子原语实现了一个包装器。

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

包装类

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> &param){
    for (vector<AtomicInt>::iterator it = param.begin();
        it != param.end(); ++it){
        *it += AtomicInt(10);
    }
}

void add100(vector<AtomicInt> &param){
    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&lt;int&gt; 对此进行了测试,它没有任何Failed 输出,但这可能只是巧合。

【问题讨论】:

  • 您的AtomicVariable 不仅毫无意义,而且还非常有害。它需要std::atomic,并使其某些操作成为非原子操作。特别是,std::atomic&lt;int&gt;::operator+= 是原子的,而 AtomicInt::operator+= 不是。

标签: multithreading c++11 containers atomic


【解决方案1】:

只需将运算符 += 写为:

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

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

你的例子失败了,因为下面的执行场景是可能的:

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

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

【讨论】:

  • 或者,就此而言,只需删除AtomicVariable 并直接使用std::atomic&lt;int&gt;。包装器似乎完全没有意义。
  • 现在正在调查。 @IgorTandetnik 这是必需的,因为运算符没有隐式转换。可能是我编码错误,但我尝试使用 std::atomic 它没有编译。
  • @JohnJohn 有AtomicVariable::operator+= 调用底层atomic::operator+=。如atomic += rhs.atomic.load()。或atomic.fecth_add(rhs.atomic.load())。这应该会有所帮助。
  • 阅读this link的文档不知道,谢谢指出。
【解决方案2】:

请注意

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

不是原子的

您有两种选择来解决它。 记忆 1) 使用互斥锁。

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

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

memory_order_acquire:保证后续加载不会在当前加载或任何先前加载之前移动。 memory_order_release:前面的存储不会移过当前存储或任何后续存储。

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

【讨论】:

  • 这毫无意义。是的,你展示了两条指令——但每条指令都涉及到自己单独的内存位置。对于“非原子”的东西,应该对同一内存位置进行两次读取和/或修改。真正的问题在于operator+=atomic.store(rhs.atomic.load() + atomic.load()); 这在同一个原子上调用loadstore
  • @IgorTandetnik - 这两条指令是问题所在。如果顺序是 T1 加载 T2 加载 T2 存储 T1 加载存在问题,因为它们触及相同的内存。
  • 我不明白你的符号。无论如何,您的示例从一块内存加载并存储到另一块内存;它们不会“触及相同的记忆”。
  • 是的,+= 不是原子的。但你似乎指向operator=,而不是operator+=,是罪魁祸首——我看不出operator=有什么问题。事实上,std::atomic&lt;T&gt;::operator= 的作用完全一样。
  • 对内存顺序的含糊态度是无用的,也是错误的。默认情况下,std::atomic 已经使用了最强的内存顺序 (memory_order_seq_cst)。
猜你喜欢
  • 2012-10-07
  • 1970-01-01
  • 2019-10-02
  • 2013-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-07
相关资源
最近更新 更多