Некоторое время я читал, чтобы лучше понять, что происходит при многопоточном программировании с современным (многоядерным) процессором. Тем не менее, пока я читал этот, Я заметил код ниже в разделе «Явные барьеры компилятора», который не использует volatile для IsPublished
Глобальный.
#define COMPILER_BARRIER() asm volatile("" ::: "memory")
int Value;
int IsPublished = 0;
void sendValue(int x)
{
Value = x;
COMPILER_BARRIER(); // prevent reordering of stores
IsPublished = 1;
}
int tryRecvValue()
{
if (IsPublished)
{
COMPILER_BARRIER(); // prevent reordering of loads
return Value;
}
return -1; // or some other value to mean not yet received
}
Вопрос в том, безопасно ли исключать летучие для IsPublished
Вот? Многие люди упоминают, что ключевое слово «volatile» не имеет ничего общего с многопоточным программированием, и я с ними согласен. Тем не менее, во время оптимизации компилятора «Constant Folding / Propagation» может применяться и как вики-страница показывает, что можно изменить if (IsPublished)
в if (false)
если компилятор не знает много о том, кто может изменить значение IsPublished
, Я что-то здесь упускаю или неправильно понял?
Барьеры памяти могут помешать упорядочению компилятора и неупорядоченному выполнению для CPU, но, как я сказал в предыдущем параграфе, мне все еще нужно volatile
чтобы избежать «константного свертывания / распространения», которое является опасной оптимизацией, особенно с использованием глобалов в качестве флагов в коде без блокировки?
Если tryRecvValue()
вызывается один раз, его можно опустить летучий за IsPublished
, То же самое верно в случае, когда между вызовами tryRecvValue()
есть вызов функции, для которого компилятор не может доказать, что он не меняется ложный ценность IsPublished
,
// Example 1(Safe)
int v = tryRecvValue();
if(v == -1) exit(1);
// Example 2(Unsafe): tryRecvValue may be inlined and 'IsPublished' may be not re-read between iterations.
int v;
while(true)
{
v = tryRecvValue();
if(v != -1) break;
}
// Example 3(Safe)
int v;
while(true)
{
v = tryRecvValue();
if(v != -1) break;
some_extern_call(); // Possibly can change 'IsPublished'
}
Постоянное распространение может применяться только тогда, когда компилятор может доказать значение переменной. Так как IsPublished
объявлен как непостоянный, его значение может быть доказано, только если:
Переменная читается (снова) в поток той же программы.
Между 2 и 3 переменная не изменяется в потоке данной программы.
Если вы не позвоните tryRecvValue()
в какой-то функции .init компилятор никогда не увидит IsPublished инициализация в том же потоке с его чтением. Итак, доказательство ложный Значение этой переменной согласно ее инициализации невозможно.
доказывания ложный ценность IsPublished в соответствии с ложный (пустая) ветка в tryRecvValue
функция возможна, см. Example 2
в коде выше.