В то время как цикл с пустым телом проверяет изменчивые ints - что это значит?
Я рассматриваю класс С++, который имеет следующие строки:
while( x > y );
return x - y;
x
и y
являются переменными-членами типа volatile int
. Я не понимаю эту конструкцию.
Я нашел здесь заглушку кода: https://gist.github.com/r-lyeh/cc50bbed16759a99a226. Я предполагаю, что это не гарантировано, будет правильным или даже работать.
Ответы
Ответ 1
Поскольку x
и y
объявлены как volatile, программист ожидает, что они будут изменены извне программы.
В этом случае ваш код останется в цикле
while(x>y);
и вернет значение x-y
после изменения значений извне, чтобы x <= y
. Точную причину, почему это написано, можно угадать, когда вы расскажете нам больше о своем коде и о том, где вы его видели. Цикл while
в этом случае является методом ожидания для другого события.
Ответ 2
Кажется,
while( x > y );
представляет собой цикл . Он не остановится до x <= y
. Поскольку x
и y
являются volatile
, они могут быть изменены за пределами этой процедуры. Итак, как только x <= y
станет истинным, возвращается x - y
. Этот метод используется для ожидания какого-либо события.
Обновление
В соответствии с gist, который вы добавили, кажется, идея заключалась в том, чтобы реализовать поточно-безопасный кольцевой буфер без блокировки. Да, реализация неверна. Например, исходный фрагмент кода
unsigned count() const {
while( tail > head );
return head - tail;
}
Даже если tail
становится меньше или равно head
, не гарантируется, что head - tail
возвращает положительное число. Планировщик может переключать выполнение на другой поток сразу после цикла while
, и этот поток может изменить значение head
. Во всяком случае, есть много других проблем, связанных с тем, как чтение и запись в работу с общей памятью (переупорядочение памяти и т.д.), Поэтому просто игнорируйте этот код.
Ответ 3
В других ответах уже подробно указано, что делает инструкция, но просто для того, чтобы напомнить, что, поскольку y
(или head
в связанном примере) объявляется как volatile
изменения, внесенные в эту переменную из другого thread приведет к завершению цикла while
после выполнения условия.
Однако, хотя пример связанного кодa > очень короткий, это почти идеальный пример того, как НЕ писать код.
Прежде всего, строка
while( tail > head );
будет тратить огромное количество циклов процессора, в значительной степени блокируя одно ядро до тех пор, пока условие не будет выполнено.
Код становится еще лучше, когда мы идем.
buffer[head++ % N] = item;
Спасибо JAB за указание, что я ошибся с post-pre-increment здесь. Исправлены последствия.
Поскольку нет lock
или mutex
es, мы, очевидно, должны будем принять худшее. Поток переключится после назначения значения в item
и до выполнения head++
. Затем Мерфи снова вызовет функцию, содержащую это утверждение, присвоив значение item
в той же позиции head
.
После этого head
увеличивается. Теперь мы снова возвращаемся к первому потоку и увеличиваем head
. Поэтому вместо
buffer[original_value_of_head+1] = item_from_thread1;
buffer[original_value_of_head+2] = item_from_thread2;
мы получим
buffer[original_value_of_head+1] = item_from_thread2;
buffer[original_value_of_head+2] = whatever_was_there_previously;
Вы можете избавиться от неаккуратного кодирования, как это на стороне клиента, с несколькими потоками, но на стороне сервера это можно считать только тикающей бомбой замедленного действия. Вместо этого используйте конструкции синхронизации, такие как lock
или mutex
es.
И хорошо, только ради полноты, линия
while( tail > head );
в методе pop_back()
должно быть
while( tail >= head );
если вы не хотите, чтобы вы могли добавить еще один элемент, чем вы на самом деле нажали (или даже поместить один элемент, прежде чем нажимать что-нибудь).
Извините за то, что в основном сводится к длинному разглагольству, но если это держит только одного человека от копирования и вставки этого непристойного кода, это стоит того.
Обновление: Думаю, я мог бы также привести пример, где код типа while(x>y);
действительно имеет смысл.
На самом деле вы часто видели такой код довольно часто в "добрые старые" дни. кашель ДОС.
Однако не использовался в контексте потоков. В основном, в качестве резервной копии в случае, когда регистрация прерывания была невозможна (вы, дети, могли бы перевести это как "невозможно зарегистрировать обработчик событий" ).
startAsynchronousDeviceOperation(..);
Это может быть почти что угодно, например. сообщите жесткому диску о чтении данных через DMA или сообщите звуковой карте для записи через DMA, возможно, даже вызовите функции на другом процессоре (например, в графическом процессоре). Обычно инициируется с помощью outb(2)
.
while(byteswritten==0); // or while (status!=DONE);
Если единственным каналом связи с устройством является разделяемая память, то пусть будет так. Не ожидал бы увидеть код, подобный этому в настоящее время вне драйверов устройств и микроконтроллеров. Очевидно, предполагает, что в спецификациях указано, что ячейка памяти является последней, на которую написано.
Ответ 4
Ключевое слово volatile
предназначено для предотвращения определенных оптимизаций. В этом случае, без ключевого слова, компилятор может развернуть цикл while
в конкретную последовательность инструкций, которые, очевидно, будут нарушаться в действительности, поскольку значения могут быть изменены извне.
Представьте себе следующее:
int i = 2;
while (i-- > 0) printf("%d", i);
Большинство компиляторов рассмотрят это и просто сгенерируют два вызова printf
- добавление ключевого слова volatile
приведет к тому, что вместо этого он будет генерировать инструкции CPU, которые вызывают счетчик, установленный в 2, и проверку значения после каждой итерации.
Например,
volatile int i = 2;
this_function_runs_on_another_process_and_modifies_the_value(&i);
while(i-- > 0) printf("%d", i);