Теоретически, volatile
недостаточно. Есть два уровня абстракции:
- между действиями исходного кода и фактическими кодами операций;
- между тем, что видит ядро / процессор, и тем, что видят другие ядра / процессоры.
Компилятор может кэшировать данные в регистре и изменять порядок чтения и записи. Используя volatile
, вы указываете компилятору создавать коды операций, которые выполняют чтение и запись точно в порядке, указанном в исходном коде. Но это касается только первого слоя. Аппаратная система, которая управляет связью между ядрами процессора, может также задерживать и изменять порядок чтения и записи.
Так получилось, что на оборудовании x86 ядра распространяют записи в основную память довольно быстро, а другие ядра автоматически уведомляются об изменении памяти. Так что volatile
кажется достаточным: он гарантирует, что компилятор не будет играть в забавные игры с регистрами, а система памяти достаточно любезна, чтобы справиться с этим вопросом. Обратите внимание, что это не так во всех системах (я думаю, что по крайней мере некоторые системы Sparc могут задерживать распространение записи из-за произвольных задержек - возможно, часов), и я прочитал в одном из руководств AMD, что AMD явно оставляет за собой право на Распространение пишет менее быстро в некоторых будущих процессорах.
Таким образом, чистое решение - использовать мьютекс (pthread_mutex_lock()
в Unix, EnterCriticalSection()
в Windows) при каждом обращении к вашей глобальной переменной (как для чтения, так и для записи). Примитивы Mutex включают специальную операцию, известную как барьер памяти , которая похожа на volatile
на стероидах (она действует как volatile
для обоих уровней абстракции).