дополнение к std::atomic‹double› не суммируется с неатомарным аналогом

Я пытаюсь выполнить добавление к двойному атомарно в цикле, используя схему сравнения и обмена с этой функцией:

namespace my
{
    template<typename value_type>
    value_type atomic_add(std::atomic<value_type>& operand, value_type value_to_add)
    {
        value_type old = operand.load(std::memory_order_consume);
        value_type desired = old + value_to_add;
        while (!operand.compare_exchange_weak(old, desired, std::memory_order_release, std::memory_order_consume))
            desired = old + value_to_add;

        return desired;
    }
}

Это используется в моем коде так:

[ size of containers = 62, scalar = 318.0, values in containers between 0.0 and 55.0, all values are of type double ]

for(size_t i = 0; i < container.size(); i++)
{
    my::atomic_add<double>(Q, container2[i] - std::pow(container3[i], 2) / scalar);
}

Выход 0.57784502195324539.

Однако замена всех my_atomic на оператор += и замена всех std::atomic<double> на double дает 0.52something, что ближе к тому, что я ожидал.

Любая идея, почему это происходит?

Спасибо.


person Vicks    schedule 30.11.2016    source источник
comment
Добро пожаловать обратно в Stack Overflow! :) Я думаю, нам понадобится минимально воспроизводимый пример, чтобы понять это.   -  person mindriot    schedule 30.11.2016
comment
Предоставлю, спасибо!   -  person Vicks    schedule 30.11.2016
comment
Вы не можете использовать std::atomic_fetch_add ?   -  person Arunmu    schedule 30.11.2016
comment
@Arunmu К сожалению, нет; std::atomic_fetch_add работает только для целочисленных типов. В этом случае мне нужны поплавки.   -  person Vicks    schedule 01.12.2016


Ответы (1)


Кажется, я исправил эту проблему, поэтому я собираюсь опубликовать ответ в качестве предостережения для других разработчиков.

Вот минимальный рабочий пример:

#include <iostream>

#include <atomic>
#include <vector>

namespace my
{
    template<typename value_type>
    value_type atomic_add(std::atomic<value_type>& operand, value_type value_to_add)
    {
        value_type old = operand.load(std::memory_order_consume);
        value_type desired = old + value_to_add;
        while (!operand.compare_exchange_weak(old, desired, std::memory_order_release, std::memory_order_consume))
            desired = old + value_to_add;

        return desired;
    }
}

int main()
{
    std::vector<double> vector;

    for(int i = 0; i < 100; i++)
    {
        double a;
        a = 2.0;
        vector.push_back(a);
    }

    std::atomic<double> d;

    for(int i = 0; i < vector.size(); i++)
        my::atomic_add(d, vector[i]);

    std::cout << d << std::endl;
}

Выход которого 202. Похоже, что d инициализируется как 2.0. Мораль этой истории заключается в том, чтобы всегда следить за тем, чтобы ваши std::atomic были инициализированы до разумного значения.

Кроме того, в других ответах указывалось, что обычно не рекомендуется хранить std::atomics в векторе, поскольку они не являются CopyAssignable и CopyConstructible.

person Vicks    schedule 30.11.2016
comment
Я бы порекомендовал вам отредактировать свой вопрос, чтобы он содержал пример. Таким образом, настоящей причиной вашей проблемы является неопределенное поведение: как вы заметили, std::atomic ctor по умолчанию не инициализирует данные. d оказывается равным 2.0, потому что значение уже находится в этом месте стека из предыдущего цикла (double a удерживается на том же месте). Если вы измените свой код на a = 4.0, в результате вы, скорее всего, получите 404. Но это как раз то, что делает ваш компилятор; на самом деле, вы просто видите UB. - person mindriot; 01.12.2016
comment
См. также stackoverflow.com/questions /36320008/ (ваш пост оказался дубликатом). - person mindriot; 01.12.2016
comment
@mindriot Я внесу соответствующие поправки; Это оказалось дубликатом, однако мое первое подозрение заключалось в том, что это была второстепенная ошибка с плавающей запятой. Оглядываясь назад, это было наивным предположением. - person Vicks; 01.12.2016