C ++: Может ли объект храниться и не храниться? - PullRequest
0 голосов
/ 06 июня 2018

Я был в дебатах по поводу углового случая, касающегося локальных переменных в многопоточной среде.

Вопрос касается программ, сформированных как:

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, и выполнить переупорядочение?

Ответы [ 3 ]

0 голосов
/ 07 июня 2018

Компилятору разрешено выполнять оптимизацию до тех пор, пока наблюдаемые результаты выполнения программы могут быть получены законным образом ("как если бы") без этих оптимизаций. [1] Таким образом, этот вопрос использует "как-if "вводящим в заблуждение образом, если на самом деле не задает обратный вопрос:

Разрешено ли компилятору хранить i, но переупорядочивать так, как если бы i фактически не сохранялось?

Это спрашивает, разрешено ли компилятору что-либо делать, если результаты выполнения программы могли быть получены с оптимизацией.Это не вопрос, чтобы задать.Вопрос должен использовать неоптимизированное поведение в качестве ссылки.Итак, что-то вроде: «Разрешено ли компилятору переупорядочивать операторы?» Ответ - да, до тех пор, пока наблюдаемые результаты не изменятся.Ничему внешнему по отношению к этой конкретной функции не сказано, как получить доступ к i, поэтому компилятору должно быть разрешено реализовывать приращение в любом месте между окружающими его использованиями (в частности, его определением и оператором return).

* 1018При этом, я ожидаю, что компилятор в этом случае не даст ни i адрес памяти, ни обработает его как переменную регистра.Я ожидаю, что компилятор будет воспринимать его как константу, эффективно изменяя вашу функцию следующим образом:
int main()
{
    doSomethingWhichMaySpawnAThreadAndUseTheMutex();
    mut.lock();
    mut.unlock();
    return 1;
}

Это разрешено, если у вас нет возможности обнаружить, что это было сделано (если не рассматривать машинунепосредственно код).

Примечание:
[1] Использование слова «могло быть» является подтверждением того, что существуют части спецификации C ++, которыеиспользуйте слово «не указано».Эти части позволяют компиляторам делать выбор, который (при работе с ненадежным кодом) может изменить наблюдаемое поведение.То есть может быть набор разрешенных поведений, а не одно допустимое поведение.Пока результаты остаются в этом наборе, оптимизация разрешена.

0 голосов
/ 08 июня 2018

Вся последовательность:

    mut.lock(); // i == 0 at that point
    i += 1;        // can this be reordered?
    mut.unlock(); // i == 1 at that point
    exit(i);
}

может быть скомпилирована как просто exit(1);, а другие потоки могут быть проигнорированы, так как нет правильной синхронизации.

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

Мьютекс не играет здесь никакой значимой роли.

0 голосов
/ 07 июня 2018

Я нахожу этот вопрос очень запутанным.С кодом, который был опубликован, компилятор, очевидно, осознает, что единственное использование i находится в операторе return, поэтому i будет оптимизировано, конец истории.Мьютекс не входит в него.

Но как только вы берете адрес i - и отдаете его кому-то другому - игра меняется.Теперь компилятор должен поместить реальную переменную в стек и манипулировать ею только между mutex.lock() и mutex.unlock().Выполнение чего-либо еще изменит семантику вашей программы.Мьютекс также дает вам забор памяти.

Вы можете ясно видеть это в Godbolt .

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...