Когда разные потоки используют только несвязанные объекты и буквально не делятся чем-либо, они не могут иметь расы, верно? Очевидно.
На самом деле все потоки имеют что-то общее: адресное пространство. Нет гарантии, что область памяти, которая использовалась одним потоком, не будет выделена в другое время другому потоку. Это может быть справедливо для памяти для динамически распределенных объектов или даже для автоматических объектов: не существует предписания, что пространство памяти для «стеков» (локальных объектов функций) нескольких потоков предварительно выделяется (даже лениво), не пересекается и представлен как обычный линейный «стек»; это может быть что угодно с поведением стека (FILO). Таким образом, область памяти, используемая для хранения автоматического объекта, может быть позже использована другим автоматическим объектом в другом потоке.
Это само по себе кажется довольно безобидным и неинтересным, поскольку то, как создается пространство для автоматических объектов, важно только при отсутствии места (очень большие автоматические массивы или глубокая рекурсия).
А как насчет синхронизации? Несвязанные непересекающиеся потоки, очевидно, не могут использовать какой-либо примитив синхронизации C ++ для обеспечения правильной синхронизации, так как по определению нет ничего (для) синхронизации (вкл), поэтому не происходит до того, как между потоками будет создано отношение .
Что, если реализация повторно использует диапазон памяти стека foo()
(включая расположение i
) после уничтожения локальных переменных и выхода из foo()
в потоке 1, чтобы сохранить переменные для bar()
в потоке 2
void foo() { // in thread 1
int i;
i = 1;
}
void bar() { // in thread 2
int i;
i = 2;
}
Не происходит до между i = 1
и i = 2
.
Это может вызвать гонку данных и неопределенное поведение?
Другими словами, могут ли все многопоточные программы иметь неопределенное поведение, основанное на вариантах реализации, над которыми пользователь не имеет контроля, которые непредсказуемы и с расами, с которыми он ничего не может поделать?