Andrei AlexandrescuОригинал статьи:
http://www.ddj.com/cpp/184403766Ключевое слово
volatile было разработано для отключения компиляторной оптимизации, которая могла бы привести к неверной работе кода в многопоточном окружении. К примеру: если переменная базового типа объявлена как volatile, то компилятору не разрешается кэшировать ее в регистре - распостраненная оптимизация, которая может привести к катастрофическим результатам, если данная переменная используется в нескольких потоках. Так что общее правило - если у вас есть переменные базовых типов, которые нужно использовать в нескольких потоках - объявляйте их как volatile. Однако возможности volatile намного шире: вы можете использовать его для нахождения не thread-safe кода и делать это можно в compile time. В статье показано как это сделать; в решении используется простой smart pointer, который также облегчает сериализацию критических секций кода.
Не хочу никому портить настроение, но эта статья затрагивает ужасную тему многопоточного программирования. В соответствии с принципами generic programming, достаточно сложное само по себе exception-safe программирование в сравнении с многопоточным - детские игрушки.
Хорошо известно, что программы, использующие несколько потоков, тяжело писать, тестировать, отлаживать, поддерживать и вообще иметь с ними дело с ними достаточно скушно. Подобная программа, содержащая ошибку, может работать годы без сбоев и внезапно рухнуть только благодаря внезапно возникшим новым условиям.
Излишне говорить, что программисту, пишущему многопоточную программу необходима вся помощь которую он может получить. В этой статье основное внимание уделяется race conditions - распространенному источнику неприятностей в многопоточном программировании - и предлагаются идеи и инструменты, помогающие решить данную проблему и, что достаточно удивительно, заставить компилятор прийти на помощь в ее решении.
Всего лишь небольшое ключевое словоAlthough both C and C++ Standards are conspicuously silent when it comes to threads, they do make a little concession to multithreading, in the form of the volatile keyword.
Just like its better-known counterpart const, volatile is a type modifier. It's intended to be used in conjunction with variables that are accessed and modified in different threads. Basically, without volatile, either writing multithreaded programs becomes impossible, or the compiler wastes vast optimization opportunities. An explanation is in order.
Рассмотрим следующий код:
class Gadget
{
public:
void Wait()
{
while (!flag_)
{
Sleep(1000); // sleeps for 1000 milliseconds
}
}
void Wakeup()
{
flag_ = true;
}
...
private:
bool flag_;
};
The purpose of Gadget::Wait above is to check the flag_ member variable every second and return when that variable has been set to true by another thread. At least that's what its programmer intended, but, alas, Wait is incorrect. Suppose the compiler figures out that Sleep(1000) is a call into an external library that cannot possibly modify the member variable flag_. Then the compiler concludes that it can cache flag_ in a register and use that register instead of accessing the slower on-board memory. This is an excellent optimization for single-threaded code, but in this case, it harms correctness: after you call Wait for some Gadget object, although another thread calls Wakeup, Wait will loop forever. This is because the change of flag_ will not be reflected in the register that caches flag_. The optimization is too ... optimistic. Caching variables in registers is a very valuable optimization that applies most of the time, so it would be a pity to waste it. C and C++ give you the chance to explicitly disable such caching. If you use the volatile modifier on a variable, the compiler won't cache that variable in registers — each access will hit the actual memory location of that variable. So all you have to do to make Gadget's Wait/Wakeup combo work is to qualify flag_ appropriately:
class Gadget
{
public:
... as above ...
private:
volatile bool flag_;
};
Most explanations of the rationale and usage of volatile stop here and advise you to volatile-qualify the primitive types that you use in multiple threads. However, there is much more you can do with volatile, because it is part of C++'s wonderful type system.
Перевод: Andrew Selivanov for crossplatform.ru