Это зависит от того, что вы подразумеваете под «целью - просто прочитать текущее значение _ServerState», и это зависит от того, какой набор инструментов и платформу вы используете (вы указываете Win32 и C ++, но не какой компилятор C ++, и что может иметь значение).
Если вы просто хотите прочитать значение таким образом, чтобы оно не было повреждено (т. Е. Если какой-либо другой процессор меняет значение с 0x12345678 на 0x87654321, то ваше чтение получит одно из этих 2 значений, а не 0x12344321), тогда просто чтение будет будьте в порядке, пока переменная:
- отмечено
volatile
,
- правильно выровнен и
- читать, используя одну инструкцию с размером слова, который процессор обрабатывает атомарно
Ничего из этого не обещает стандарт C / C ++, но Windows и MSVC действительно дают эти гарантии, и я думаю, что большинство компиляторов, нацеленных на Win32, также делают.
Однако, если вы хотите, чтобы ваше чтение было синхронизировано с поведением другого потока, есть некоторая дополнительная сложность. Скажем, у вас есть простой протокол «почтового ящика»:
struct mailbox_struct {
uint32_t flag;
uint32_t data;
};
typedef struct mailbox_struct volatile mailbox;
// the global - initialized before wither thread starts
mailbox mbox = { 0, 0 };
//***************************
// Thread A
while (mbox.flag == 0) {
/* spin... */
}
uint32_t data = mbox.data;
//***************************
//***************************
// Thread B
mbox.data = some_very_important_value;
mbox.flag = 1;
//***************************
Мысль о том, что поток А будет вращаться в ожидании, пока mbox.flag покажет, что mbox.data содержит действительный фрагмент информации. Поток B запишет некоторые данные в mailbox.data, а затем установит для mbox.flag значение 1 в качестве сигнала о том, что mbox.data действителен.
В этом случае простое чтение в потоке A mbox.flag может получить значение 1, даже если последующее чтение mbox.data в потоке A не получит значение, записанное потоком B.
Это связано с тем, что даже если компилятор не будет переупорядочивать запись потока B в mbox.data и mbox.flag, процессор и / или кэш могут. C / C ++ гарантирует, что компилятор сгенерирует код таким образом, что поток B запишет в mbox.data до того, как он запишет в mbox.flag, но процессор и кэш могут иметь другое представление - специальная обработка, называемая «барьеры памяти» или «приобретение и семантика релиза »должна использоваться для обеспечения порядка ниже уровня потока инструкций потока.
Я не уверен, что компиляторы, кроме MSVC, предъявляют какие-либо претензии относительно порядка ниже уровня инструкции. Однако MS гарантирует, что для MSVC достаточно volatile - MS указывает, что volatile записи имеют семантику выпуска, а volatile чтения приобретают семантику - хотя я не уверен, к какой версии MSVC это применимо - см. http://msdn.microsoft.com/en-us/library/12a04hfd.aspx?ppud=4.
Я также видел код, подобный описанному вами, который использует Interlocked APIs для выполнения простых операций чтения и записи в общие местоположения. Мое мнение по этому вопросу заключается в использовании взаимосвязанных API. Блокировка без связи между потоками полна очень трудных для понимания и тонких ловушек, и попытка сократить критический фрагмент кода, который может привести к очень сложной диагностике ошибки, не кажется мне хорошей идеей , Кроме того, используя Interlocked API, выкрикивает всем, кто поддерживает код: «Это доступ к данным, который необходимо совместно использовать или синхронизировать с чем-то другим - осторожно! ».
Также при использовании Interlocked API вы берете на себя специфику аппаратного обеспечения и компилятора - платформа гарантирует, что все эти вещи обрабатываются должным образом - не более чем интересно ...
Прочтите Статьи Херба Саттера об эффективном параллелизме о DDJ (которые сейчас не работают, по крайней мере для меня) для получения полезной информации по этой теме.