Использование логической переменной volatile для ожидания ожидания
Вопрос возникает после прочтения некоторых кодов, написанных другими разработчиками, поэтому я провел небольшое исследование и нашел статью Андрея Александреску. В своей статье он говорит, что можно использовать переменную логической переменной для занятого ожидания (см. Первый пример с Wait/Wakeup)
class Gadget
{
public:
void Wait()
{
while (!flag_)
{
Sleep(1000); // sleeps for 1000 milliseconds
}
}
void Wakeup()
{
flag_ = true;
}
...
private:
bool flag_;
};
Я действительно не понимаю, как это работает.
- volatile не гарантирует, что операции будут атомарными. Практически чтение / запись в логические переменные являются атомарными, но теория не гарантирует этого. С моей точки зрения, вышеприведенный код можно было бы безопасно переписать с помощью C++11, используя функции std::atomic::load/store с ограничениями упорядочения в памяти получения / освобождения памяти соответственно.
- У нас нет этой проблемы в описанном примере, но если у нас более одной записи, у нас могут быть проблемы с упорядочением памяти. Volatile не является забором, она не требует упорядочения памяти, она просто предотвращает оптимизацию компилятора.
Так почему же так много людей используют изменчивый bool для занятого ожидания и действительно ли он переносим?
2 ответа
В статье не говорится, что volatile
это все, что вам нужно (на самом деле, это не так), только то, что это может быть полезно.
Если вы делаете это, и если вы используете простой общий компонент
LockingPtr
Вы можете написать поточно-ориентированный код и меньше беспокоиться о состоянии гонки, потому что компилятор будет беспокоиться за вас и будет старательно указывать на те места, где вы ошибаетесь.
Я действительно не понимаю, как это работает.
Он опирается на два предположения:
- чтение и запись в логические переменные являются атомарными;
- все потоки имеют единое представление о памяти, поэтому изменения, сделанные в одном потоке, будут видны другим в течение короткого промежутка времени без явного барьера памяти.
Первый, вероятно, будет придерживаться любой разумной архитектуры. Второе относится к любой одноядерной архитектуре и к многоядерным архитектурам, широко используемым сегодня, но нет никакой гарантии, что она сохранится в будущем.
приведенный выше код можно безопасно переписать с помощью C++11, используя
std::atomic
Сегодня это может и должно быть. В 2001 году, когда была написана статья, не так уж много.
если у нас более одной записи, у нас могут быть проблемы с упорядочением памяти
В самом деле. Если этот механизм используется для синхронизации с другими данными, то мы полагаемся на третье предположение: этот порядок изменений сохраняется. Опять же, большинство популярных процессоров дают такое поведение, но нет никаких гарантий, что это будет продолжаться.
почему так много людей используют изменчивый bool для занятого ожидания
Потому что они не могут или не изменят привычки, которые они сформировали до того, как C++ приобрел модель многопоточной памяти.
и это действительно портативный?
Нет. Модель памяти C++11 не гарантирует ни одно из этих предположений, и есть большая вероятность того, что они станут непрактичными для поддержки будущего оборудования по мере роста типичного числа ядер. volatile
никогда не был решением для синхронизации потоков, и теперь это вдвойне важно, поскольку язык действительно обеспечивает правильные решения.