2015-05-27 8 views
5

Я не уверен, как гарантии упорядочения памяти для атомных переменных в C++ 11 влияют на операции в другой памяти.C++ 11 Атомный порядок памяти с неатомными переменными

Предположим, у меня есть один поток, который периодически вызывает функцию записи, чтобы обновить значение, и другой поток, который вызывает чтение, чтобы получить текущее значение. Гарантировано ли, что эффекты d = value; не будут видны до эффектов a = version; и будут видны до эффектов b = version;?

atomic<int> a {0}; 
atomic<int> b {0}; 
double d; 

void write(int version, double value) { 
    a = version; 
    d = value; 
    b = version; 
} 

double read() { 
    int x,y; 
    double ret; 
    do { 
     x = b; 
     ret = d; 
     y = a; 
    } while (x != y); 
    return ret; 
} 
+0

В вашем коде нет никаких барьеров; почему вы отметили его «барьерами памяти»? –

+1

Поскольку я думал, что ответ может быть чем-то вроде «это неверно, вам нужно использовать барьер памяти». –

+1

Возможно, вы захотите изменить то, что вы просите. Люди, отвечающие на этот вопрос, отвратительно педантичны и, вероятно, приносят больше вреда, чем пользы для тех, кому это интересно. –

ответ

1

Это гарантирует, что эффекты d = value; не будут рассматриваться до того воздействия a = version;, и будет видно, до эффектов b = version;?

Да, это так. Это связано с тем, что секвенциальный барьер согласованности подразумевается при чтении или записи переменной atomic<>.

Вместо того чтобы хранить version тег в два атомных переменные перед изменением значения и после этого, вы можете увеличивать одного атомных переменные до и после изменения:

atomic<int> a = {0}; 
double d; 

void write(double value) 
{ 
    a = a + 1; // 'a' become odd 
    d = value; //or other modification of protected value(s) 
    a = a + 1; // 'a' become even, but not equal to the one before modification 
} 

double read(void) 
{ 
    int x; 
    double ret; 
    do 
    { 
     x = a; 
     ret = value; // or other action with protected value(s) 
    } while((x & 2) || (x != a)); 
    return ret; 
} 

Это известно как seqlock в ядро Linux: http://en.wikipedia.org/wiki/Seqlock

2

Ваш объект d записывается и считывается двумя потоками, и это не атомная. Это небезопасно, как это было предложено в стандарте С ++ на многопоточности:

1,10/4 Два выражения оценки конфликт, если один из них изменяет ячейку памяти, а другой доступ или изменяет ту же ячейку памяти.

1,10/21 Выполнение программы содержит гонки данных, если она не содержит два противоречивых действий в разных потоках, по меньшей мере, один из , который не является атомным, и ни происходит до другого. Любая такая гонка данных приводит к неопределенному поведению.

Важны редактировать:

В вашем неатомарном случае, вы не имеете никаких гарантий относительно упорядочения между чтением и записью. У вас даже нет гарантии, что читатель прочитает значение, написанное автором (это short article explains the risk для неатомных переменных).

Тем не менее, заканчивает цикл вашего читателя на основе теста окружающих атомарных переменных, для которых есть сильные гарантии. Если предположить, что version никогда не повторяется между писателем различными вызовами, и учитывая обратный порядок, в котором вы aquire их значение:

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

Это означает, что в случае неблагоприятного состояния гонки на вашем неатомном, благодаря петле, вы закончите чтение последних value.

+0

К счастью, стандарт никогда не употребляет слово «небезопасно» нормативно. Лучший способ описать программу OP будет «неправильным» или «сломанным». –

+0

Просто потому, что оценки «конфликт» не означают, что это гонка данных (и, следовательно, UB). Вам нужна лучшая цитата. –

+0

@ KerrekSB извините за мой неоднозначный стиль. Я хотел подчеркнуть, что это небезопасно и относится к стандарту. Я изменил его на «предложил». Разве это не было бы неопределенным или неопределенным, а не сломанным? – Christophe

3

Правило гласит, что, учитывая write поток, который выполняется один раз, и больше ничего, что изменяет a, b или d,

  • Вы можете прочитать a и b из другого потока в любой момент времени, и
  • если вы читаете b и увидеть version хранящуюся в нем, то
    • вы можете прочитать d; и
    • То, что вы прочтете, будет value.

Обратите внимание, что ли вторая часть действительно зависит от порядка памяти; это верно по умолчанию (memory_order_seq_cst).

+0

Если читатель видит ту же версию для a и b, я не гарантированно прочитал значение, которое было установлено с этой версией? По моему мнению, у меня есть эта гарантия, если только эффект 'd = value' не может быть видимым перед' a = version' в вызове для записи. –

+0

@JeffreyDallaTezza Дело в том, что вы не можете прочитать 'd', если не знаете, что запись закончена, и вы делаете это, читая' b' и видя, что в нем хранится 'version'. Ваш код читается с 'd' безоговорочно, что вызывает гонку данных и, следовательно, неопределенное поведение. –