Безопасность потоков C ++ 11 атомарных контейнеров

Я пытаюсь реализовать потокобезопасный вектор 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<int>, у него не было Failed вывода, но это могло быть просто совпадением.


person JohnJohn    schedule 04.01.2015    source источник
comment
Ваш AtomicVariable не просто бессмысленен - ​​он активно вреден. Он принимает std::atomic и делает некоторые из его операций неатомарными. В частности, std::atomic<int>::operator+= является атомарным, а AtomicInt::operator+= - нет.   -  person Igor Tandetnik    schedule 04.01.2015


Ответы (2)


Просто напишите оператор + = как:

    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); Тема2 - atomic.store (100)

Наконец, в этом случае атомарное значение может быть 10 или 100, в зависимости от того, какой поток сначала выполняет atomic.store.

person AdamF    schedule 04.01.2015
comment
Или, если на то пошло, просто отбросьте AtomicVariable и используйте std::atomic<int> напрямую. Обертка кажется совершенно бессмысленной. - person Igor Tandetnik; 04.01.2015
comment
Изучаю это прямо сейчас. @IgorTandetnik требуется, потому что для операторов нет неявного преобразования. Может быть, я неправильно закодировал, но я пробовал делать с std :: atomic ‹int›, он не компилировался. - person JohnJohn; 04.01.2015
comment
@JohnJohn Заставьте AtomicVariable::operator+= вызвать базовый atomic::operator+=. Как в atomic += rhs.atomic.load(). Или atomic.fecth_add(rhs.atomic.load()). Это должно помочь. - person Igor Tandetnik; 04.01.2015
comment
Прочтите документацию на странице this link, не знал об этом, спасибо за указание . - person JohnJohn; 04.01.2015

обратите внимание, что

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

не атомарен

У вас есть два варианта решения. памятка 1) Используйте мьютекс.

РЕДАКТИРОВАТЬ, поскольку T.C упоминал в комментариях, это не имеет значения, так как здесь будет операция load (), затем load (), затем store () (не расслабленный режим), поэтому порядок памяти здесь не связан.

2) Используйте порядок памяти http://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/

memory_order_acquire: гарантирует, что последующие загрузки не будут перемещены до текущей или любых предыдущих загрузок. memory_order_release: предыдущие хранилища не перемещаются за текущее хранилище или любые последующие хранилища.

Насчет 2 пока не уверен, но думаю, если магазины не будут параллельно, то заработает.

person Guy L    schedule 04.01.2015
comment
Это не имеет никакого смысла. Да, вы показываете две инструкции, но каждая касается своего отдельного участка памяти. Чтобы что-то не было атомарным, должно быть два чтения и / или модификации одного и того же места в памяти. Настоящая проблема находится в operator+=: atomic.store(rhs.atomic.load() + atomic.load()); Это вызывает load и store на одном и том же атомарном элементе. - person Igor Tandetnik; 04.01.2015
comment
@IgorTandetnik - Проблема в двух инструкциях. если заказ - T1 load T2 load T2 store T1 load возникает проблема, поскольку они касаются одной и той же памяти. - person Guy L; 04.01.2015
comment
Я не понимаю ваших обозначений. В любом случае ваш пример загружается из одной части памяти и сохраняется в другой; они не касаются одной и той же памяти. - person Igor Tandetnik; 04.01.2015
comment
Так что именно вы подразумеваете под не атомарным здесь? Что может измениться между тем и чем еще? Я не понимаю сценарий, который, по вашему мнению, создает проблему. - person Igor Tandetnik; 04.01.2015
comment
1) Прочтите еще раз комментарий. 2) Функция + = не является атомарной, как следует из моего ответа. в любом случае другой ответ (а также ваш комментарий) более правильный - person Guy L; 04.01.2015
comment
Да, += не атомарен. Но вы, кажется, указываете на operator=, а не на operator+=, как на виновника - и я не вижу ничего плохого в operator=. Фактически, std::atomic<T>::operator= делает то же самое. - person Igor Tandetnik; 04.01.2015
comment
Расплывчатое махание рукой о порядке памяти бесполезно и также неправильно. По умолчанию std::atomic уже использует самый сильный порядок памяти (memory_order_seq_cst). - person T.C.; 06.01.2015