0

Следующая реализация от Wikipedia:барьера памяти в реализации одного производителя одного потребителя

volatile unsigned int produceCount = 0, consumeCount = 0; 
TokenType buffer[BUFFER_SIZE]; 

void producer(void) { 
    while (1) { 
     while (produceCount - consumeCount == BUFFER_SIZE) 
      sched_yield(); // buffer is full 

     buffer[produceCount % BUFFER_SIZE] = produceToken(); 
     // a memory_barrier should go here, see the explanation above 
     ++produceCount; 
    } 
} 

void consumer(void) { 
    while (1) { 
     while (produceCount - consumeCount == 0) 
      sched_yield(); // buffer is empty 

     consumeToken(buffer[consumeCount % BUFFER_SIZE]); 
     // a memory_barrier should go here, the explanation above still applies 
     ++consumeCount; 
    } 
} 

говорит, что барьер памяти должен использоваться между линией, который обращается к буферу и линией, которая обновляет Count переменная.

Это делается для того, чтобы процессор не переупорядочивал инструкции выше забора вместе с под ним. Переменная Count не должна увеличиваться до того, как она будет использоваться для индексации в буфер.

Если забор не используется, не будет ли такое переупорядочение нарушать правильность кода? ЦП не должен выполнять приращение Count, прежде чем он будет использоваться для индексации в буфер. Процессор не заботится о зависимости данных при переупорядочении команд?

Благодаря

+1

@ user3286661: Это означает, что никакой барьер памяти не может сделать код выше четко определенного C++. Это не помогает с ответом, это объясняет, почему предпосылка вашего вопроса ошибочна. – MSalters

+0

@ user3286661 Мы не заботимся о производительности, не так ли? Это почти правильность. (Потому что производительность кода, который вращается на 'sched_yield', будет ужасной.) Это должен быть либо псевдоядерный, либо специфичный для платформы код. Не существует портативного правила для того, как 'volatile' взаимодействует с барьерами памяти и потоками. –

+0

Вопрос о концепции барьеров памяти. Мы не заботимся о производительности. – user3286661

ответ

2

похоже, что ваш вопрос: "может увеличивать размер Count и получать поправку в buffer без изменения кода?".

Рассмотрим следующий код tansformation:

int count1 = produceCount++; 
buffer[count1 % BUFFER_SIZE] = produceToken(); 

Обратите внимание, что код ведет себя так же, как оригинал один: один для чтения из энергонезависимой переменной, одной записи в энергозависимой, читать происходит перед записью, состояние программы является то же самое. Тем не менее, другие потоки будут видеть различную картину относительно порядка produceCount приращений и buffer модификаций.

Оба компилятора и процессора могут сделать это преобразование без забора памяти, поэтому вам нужно заставить эти две операции быть в правильном порядке.

3

Если забор не используется, не будет такого рода переназначения нарушают правильность кода? ЦП не должен выполнять приращение Count до того, как он будет использоваться для индексации в буфер. Процессор не заботится о зависимости данных при переупорядочении команд?

Хороший вопрос.

В C++, если используется некоторая форма барьера памяти (атомарная, мьютекс и т. Д.), Компилятор предполагает, что код является однопоточным. В этом случае правило as-if говорит, что компилятор может испускать любой код, который ему нравится, при условии, что общий наблюдаемый эффект «как если бы» ваш код выполнялся последовательно.

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

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

То, что вы на самом деле наблюдаете, - это неопределенное поведение.

+0

А как насчет расположения забора памяти. Почему это между этими двумя заявлениями? – user3286661

+0

@ user3286661 Наличие забора памяти предотвращает переупорядочение памяти, записываемой через забор, а не только в текущий поток, но также *, как это заметил другой поток *. Это важная часть. Он позволяет использовать обновление памяти в качестве сигнала по потокам. –

2

Если забор не используется, не будет ли такое переупорядочение нарушать правильность кода?

Nope. Можете ли вы построить любой переносимый код, который может отличить?

ЦП не должен выполнять приращение Count до того, как оно будет использовано для индексации в буфер. Процессор не заботится о зависимости данных при переупорядочении команд?

Почему бы и нет? Какова будет отдача понесенных расходов? Такие вещи, как комбинирование записи и спекулятивная выборка, представляют собой огромную оптимизацию, и их отключение является не стартером.

Если вы думаете, что только один должен сделать это, это просто неправда. Ключевое слово volatile не имеет определенной семантики синхронизации потоков в C или C++. Возможно, это сработает на некоторых платформах, и, возможно, это не сработает на других. В Java volatile имеет определенную семантику синхронизации потоков, но не включает в себя предоставление порядка доступа к нелетучим.

Однако барьеры памяти имеют четко определенную семантику синхронизации потоков. Мы должны убедиться, что ни один поток не может видеть, что данные доступны, прежде чем он увидит эти данные. И мы должны убедиться, что поток, который помещает данные, которые могут быть перезаписаны, не рассматривается, прежде чем поток завершится с этими данными.

Смежные вопросы