Я был в дебатах по поводу углового случая, касающегося локальных переменных в многопоточной среде.
Вопрос касается программ, сформированных как:
std::mutex mut;
int main()
{
std::size_t i = 0;
doSomethingWhichMaySpawnAThreadAndUseTheMutex();
mut.lock();
i += 1; // can this be reordered?
mut.unlock();
return i;
}
Вопрос вращается вокругi += 1
можно переупорядочить так, чтобы он находился выше блокировки мьютекса.
Очевидные детали: mut.lock()
происходит раньше i += 1
, поэтому, если какой-либо другой поток сможетсоблюдайте значение i
, компилятор обязан не увеличивать его.Из 3.9.2.3 спецификации C ++: «Если объект типа T находится по адресу A, указатель типа cv T *, значением которого является адрес A, называется , указывающим на этот объект,независимо от того, как было получено значение. "" Это означает, что если бы я использовал какие-либо средства для получения указателя на i
, я мог бы ожидать увидеть правильное значение.
Однако в спецификации указано, чтокомпилятор может использовать правило «как если», чтобы не давать объекту адрес памяти (сноска 4 в разделе 1.8.6). Например, i
может храниться в регистре, который не имеет адреса памяти.В этом случае не было бы адреса памяти, на который можно было бы указать, поэтому компилятор мог доказать, что ни один другой поток не может получить доступ к i
.
. Меня интересует вопрос: а что, если компилятор не делает этого?-if "оптимизация и действительно хранит объект. Разрешено ли компилятору хранить i
, но выполнять переупорядочение, как если бы i
фактически не хранилось? С точки зрения реализации это будет означать, что i
можетхраниться в стеке, и, таким образом, было бы возможно иметь указатель на него, но должен ли компилятор предположить, что никто не может видеть i
, и выполнить переупорядочение?