Синхронизация доступа к переменной

Мне нужно обеспечить синхронизацию для некоторых элементов структуры.
Если структура выглядит примерно так

struct SharedStruct {
    int Value1;
    int Value2;
}

и у меня есть глобальная переменная

SharedStruct obj;  

Я хочу, чтобы запись с процессора

 obj.Value1 = 5; // Processor B

быть немедленно видимым для других процессоров, так что, когда я проверяю значение

 if(obj.Value1 == 5) { DoSmth(); } // Processor A
 else DoSmthElse();   

чтобы получить новое значение, а не какое-то старое значение из кеша.
Сначала я подумал, что если я использую volatile при записи/чтении значений, этого достаточно. Но я читал, что volatile не может решить такие проблемы.
Члены гарантированно правильно выровнены по границам 2/4/8 байт, и записи в этом случае должны быть атомарными, но я не уверен, как это сделать. кеш может этому помешать.
Будет достаточно использовать барьеры памяти (mfence, sfence и т.д.)? Или требуются какие-то взаимосвязанные операции?
Или, может быть, что-то вроде

lock mov addr, REGISTER  

?
Очевидно, проще всего было бы использовать какой-нибудь запирающий механизм, но скорость имеет решающее значение, а замки не по карману :(

Изменить
Возможно, мне следует немного пояснить. Значение устанавливается только один раз (ведет себя как флаг). Всем остальным веткам нужно просто прочитать это. Вот почему я думаю, что это может быть способ принудительно прочитать это новое значение без использования блокировок.

Заранее спасибо!


person Gratian Lup    schedule 18.08.2009    source источник


Ответы (5)


Все остальные ответы здесь, похоже, говорят о сложностях обновления общих переменных с использованием мьютексов и т. Д. Это правда, что вы хотите, чтобы обновление было атомарным. И вы можете использовать различные примитивы ОС, чтобы гарантировать это, и это будет хорошим стилем программирования.

Однако на большинстве современных процессоров (конечно, на x86) запись небольших выровненных скалярных значений является атомарной и сразу видна другим процессорам из-за когерентности кэша. Так что в этом особом случае вам не нужен весь синхронизирующий хлам; аппаратное обеспечение выполняет атомарную операцию за вас. Конечно, это безопасно с 4-байтовыми значениями (например, «int» в 32-битных компиляторах C).

Таким образом, вы можете просто инициализировать Value1 неинтересным значением (скажем, 0) перед тем, как запускать параллельные потоки, и просто писать туда другие значения. Если вопрос выходит из цикла с фиксированным значением (например, если значение1 == 5), это будет совершенно безопасно.

Если вы настаиваете на захвате первого записанного значения, это не сработает. Но если у вас есть параллельный набор потоков и подойдет любое значение, кроме неинтересного, это тоже нормально.

person Ira Baxter    schedule 19.08.2009
comment
Спасибо за ответ. Что-то подобное я и надеялся услышать :) - person Gratian Lup; 19.08.2009

Бесплатных обедов не бывает. Если доступ к вашим данным осуществляется из нескольких потоков, и необходимо, чтобы обновления были немедленно видны этим другим потокам, вам необходимо защитить общую структуру с помощью мьютекса, блокировки чтения/записи или какого-либо подобного механизма.

Производительность является серьезной проблемой при синхронизации кода, но она перевешивает корректность. Вообще говоря, сначала стремитесь к правильности, а затем профилируйте свой код. Беспокойство о производительности, когда вы еще не зафиксировали правильность, является преждевременной оптимизацией.

person peterb    schedule 18.08.2009

Я второй ответ peterb, чтобы сначала стремиться к правильности. Да, вы можете использовать здесь барьеры памяти, но они не будут делать то, что вы хотите.

Вы сказали немедленно. Однако, каким бы немедленным ни было это обновление, вы можете (и будете) выполнять предложение if(), затем устанавливать флаг, а затем выполнять DoSmthElse(). Это называется состоянием гонки...

Вроде хотите что-то синхронизировать, но это не этот флаг.

person gimpf    schedule 18.08.2009
comment
Мораль: потоки очень похожи на системы отсчета в специальной теории относительности: то, что непосредственно в одном, не обязательно будет немедленным в другом ;-) - person Steve Jessop; 18.08.2009

Используйте явно атомарные инструкции. Я считаю, что большинство компиляторов предлагают их как встроенные функции. «Сравнить и обменять» — еще один хороший инструмент.

Если вы собираетесь написать алгоритм без блокировки, вам нужно написать его так, чтобы ваши изменения вступали в силу только тогда, когда условия соответствуют ожиданиям.

Например, если вы собираетесь вставить объект связанного списка, используйте материал сравнения/обмена, чтобы он вставлялся только в том случае, если указатель все еще указывает на то же место, когда вы фактически выполняете обновление.

Или, если вы собираетесь уменьшить счетчик ссылок и освободить память со счетчиком 0, вы захотите предварительно освободить ее, сделав ее каким-то образом недоступной, проверить, что счетчик по-прежнему равен 0, а затем действительно освободить ее. Или что-то типа того.

Использование блокировки, работы, разблокировки, как правило, намного проще. Безблокировочные алгоритмы действительно сложно реализовать правильно.

person Zan Lynx    schedule 18.08.2009

Изменение поля должно сделать изменение «немедленно» видимым в других потоках, но нет гарантии, что момент, когда поток A выполняет обновление, не происходит после того, как поток B проверит значение, но до того, как поток B выполнит тело оператор если/иначе.

Похоже, что вы действительно хотите сделать оператор if/else атомарным, и для этого потребуется либо блокировка, либо алгоритм, терпимый к такого рода ситуациям.

person Tyler McHenry    schedule 18.08.2009
comment
Заявление несколько атомарно... Спин-блокировка, сделанная мной (я не могу позволить себе использовать столько памяти, как CRITICAL_SECTION. Это часть распределителя памяти, который я делаю для Uni), защищает область кода, включая оператор если/иначе. Но я не уверен, что когда я читаю значение с volatile, я получаю новое значение или что-то еще из кеша. - person Gratian Lup; 18.08.2009
comment
Попытка сделать одновременный доступ к данным без использования блокировок обречена на провал. Он не будет работать. Вообще. Каждая функциональная реализация malloc во всем мире, которая когда-либо была написана с незапамятных времен, использует критические секции. На это есть причина. - person peterb; 18.08.2009
comment
volatile не решит проблемы с синхронизацией памяти. Проблема, которую решает volatile, полностью ортогональна синхронизации (за исключением Java и C#). - person deft_code; 18.08.2009
comment
@caspin ... именно это я и сказал. Моя точка зрения заключалась в том, что вопрос ОП о немедленной видимости изменения был неправильным вопросом, поскольку он может получить это, но это не решает его проблему. - person Tyler McHenry; 18.08.2009
comment
@peterb Безусловно, существуют такие вещи, как алгоритмы без блокировки и без ожидания, которые могут работать без критических разделов при наличии соответствующей аппаратной поддержки. Вопрос не дает достаточно информации о проблеме, чтобы определить, можно ли использовать такую ​​​​вещь в случае ОП. - person Tyler McHenry; 18.08.2009
comment
То, чего я пытаюсь достичь, не является алгоритмом без блокировки. Область кода заблокирована моим Spinlock. Я просто не уверен, будут ли изменения видны в других потоках (после того, как они получат ту же блокировку). Теперь мне нужно узнать, что, например, делает CRITICAL_SECTION, чтобы убедиться, что изменение видно и реализовано в моем случае с ограниченной памятью. - person Gratian Lup; 19.08.2009
comment
Тогда volatile будет тем, что вы хотите. Это заставляет компилятор изменять переменную непосредственно в памяти и считывать ее непосредственно из памяти без какой-либо формы локального кэширования. Самое последнее значение изменчивой переменной всегда будет доступно для всех потоков, хотя самое последнее значение в локальном контексте зависит от синхронизации. Если вы блокируете переменную каждый раз, когда обращаетесь к ней, то пометка ее как volatile должна быть всем, что вам нужно сделать, чтобы сохранить соглашение о том, каково ее значение. - person Tyler McHenry; 19.08.2009
comment
Я не могу понять, почему за этот ответ проголосовали отрицательно; да, это обман, но это кажется современным. Тем не менее, Igration, другим обычно полезно также обновлять вопрос, а не только давать информацию в комментариях; потому что теперь правильный ответ - это последний комментарий последнего ответа, что не может быть намерением. - person gimpf; 19.08.2009