Проблемы с упорядочением памяти - PullRequest
9 голосов
/ 25 октября 2010

Я экспериментирую с поддержкой C ++ 0x, и есть проблема, которую, как мне кажется, быть не должно.Либо я не понимаю тему, либо gcc имеет ошибку.

У меня есть следующий код, изначально x и y равны.Поток 1 всегда сначала увеличивает x, а затем y.Оба являются целочисленными атомарными значениями, поэтому с приращением проблем нет.Поток 2 проверяет, меньше ли x, чем y, и отображает сообщение об ошибке, если так.

Этот код иногда дает сбой, но почему?Проблема здесь, вероятно, заключается в переупорядочении памяти, но все атомарные операции последовательно согласованы по умолчанию, и я явно не ослаблял эти операции.Я компилирую этот код на x86, который, насколько я знаю, не должен иметь проблем с упорядочением.Не могли бы вы объяснить, в чем проблема?

#include <iostream>
#include <atomic>
#include <thread>

std::atomic_int x;
std::atomic_int y;

void f1()
{
    while (true)
    {
        ++x;
        ++y;
    }
}

void f2()
{
    while (true)
    {
        if (x < y)
        {
            std::cout << "error" << std::endl;
        }
    }
}

int main()
{
    x = 0;
    y = 0;

    std::thread t1(f1);
    std::thread t2(f2);

    t1.join();
    t2.join();
}

Результат можно посмотреть здесь .

Ответы [ 4 ]

12 голосов
/ 25 октября 2010

Существует проблема со сравнением:

x < y

Порядок вычисления подвыражений (в данном случае x и y) не указан, поэтому y может быть оценендо x или x может быть вычислено до y.

Если x читается первым, у вас возникла проблема:

x = 0; y = 0;
t2 reads x (value = 0);
t1 increments x; x = 1;
t1 increments y; y = 1;
t2 reads y (value = 1);
t2 compares x < y as 0 < 1; test succeeds!

Если вы явно гарантируете, что y читается первым, вы можете избежать проблемы:

int yval = y;
int xval = x;
if (xval < yval) { /* ... */ }
11 голосов
/ 25 октября 2010

Проблема может быть в вашем тесте:

if (x < y)

поток может оценить x и не сможет обойтись до y намного позже.

4 голосов
/ 25 октября 2010

Время от времени x будет переходить в 0 как раз перед тем, как y будет переходить в ноль.На данный момент y будет законно больше, чем x.

0 голосов
/ 25 октября 2010

Во-первых, я согласен с «Майклом Берром» и «Джеймсом МакНеллисом».Ваш тест несправедлив, и есть законная возможность потерпеть неудачу.Однако, даже если вы переписываете тест, как «Джеймс МакНеллис» предполагает, что тест может провалиться.

Первая причина этого заключается в том, что вы не используете семантику volatile, поэтому компилятор может оптимизировать ваш код(это должно быть нормально в однопоточном случае).

Но даже с volatile ваш код не гарантированно работает.

Я думаю, вы не до конца понимаетеКонцепция переупорядочение памяти .На самом деле переупорядочение чтения / записи памяти может происходить на двух уровнях:

  1. Компилятор может обмениваться порядком сгенерированных инструкций чтения / записи.
  2. CPU может выполнять инструкции чтения / записи памяти произвольным образомorder.

Использование volatile предотвращает (1).Однако вы ничего не сделали для предотвращения (2) - переупорядочение доступа к памяти с помощью оборудования .

Чтобы предотвратить это, вы должны вставить в код специальные инструкции memory fence (они предназначены для CPU, в отличие от volatile, который предназначен только для компилятора).

В x86 / x64 существует множество различных инструкций по ограничению памяти.Кроме того, каждая инструкция с семантикой lock по умолчанию создает полный забор памяти.

Более подробная информация здесь:

http://en.wikipedia.org/wiki/Memory_barrier

...