Является ли доступ к полю bool атомарным в C#? В частности, мне нужно поставить блокировку:
class Foo
{
private bool _bar;
//... in some function on any thread (or many threads)
_bar = true;
//... same for a read
if (_bar) { ... }
}
Да, но (возможно) тоже да. Да, доступ / установка поля bool является атомарным, НО операция if - нет (см. Ответ Dror Helper ниже), поэтому вам все равно может понадобиться блокировка.





Да.
Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types.
как найдено в Спецификация языка C#.
Обновлено: вероятно, также стоит понять ключевое слово летучий.
Подождите секунду ... чтение и запись в ссылочные типы (например, любой объект) атомарны?
Сам указатель, переназначающий его, является атомарным (т.е. Foo foo1 = foo2;
По моему опыту, volatile - это вообще не лучшая идея: если вам нужен volatile, вы обычно заботитесь об «одновременных» действиях - и даже с volatile довольно легко ошибиться. Например, для приведенного выше оператора if потребовалось бы немного дополнительной сантехники, чтобы избежать ввода множества потоков - и если цель состоит только в том, чтобы избежать входа до тех пор, пока какой-либо поток не пройдет _bar = true; хотя бы один раз, отказ от использования volatile в худшем случае означает, что if Оператор не будет выполняться временно, даже если _bar истинен. Я предлагаю просто использовать блокировки, если требуется точность.
@Eamon: Если дело в том, что if не следует вводить, пока поток не установит _bar = true;, тогда volatile - это именно то, что вам нужно - зачем вам здесь блокировки?
@configurator: Вопрос в том, что именно вам нужно. Легко ошибиться в программах без блокировки; поэтому, если вам это действительно не нужно, лучше использовать более простой фреймворк (например, TPL). Другими словами, «volatile» не является неправильным, а является признаком хитрого (т.е. желательно избегать) кода. OP на самом деле не сказал, что он хочет, я просто волей-неволей сомневаюсь, что рекомендую изменчив.
Вау. Это опасная формулировка, поскольку атомарность для C++ означает, что любое чтение и запись также окружены соответствующим забором памяти. Что, конечно, не так в C#. Потому что в противном случае производительность была бы ужасной, поскольку она обязательна для всех переменных <long. Атомарно здесь, на языке C#, похоже, означает когда в конечном итоге происходит чтение или запись, они гарантированно никогда не будут в неисправном состоянии. Но он ничего не говорит о том, когда наступит «в конце концов».
Если запись в int и long является атомарной, то при использовании Interlocked.Add(ref myInt);, например?
@MikedeKlerk Чтение и запись атомарны, но разделены. i++ эквивалентен i=i+1, что означает, что вы выполняете атомарное чтение, затем сложение, а затем атомарную запись. Другой поток может изменить i после чтения, но до записи. Например, два потока, выполняющие i++ одновременно на одном и том же i, могут одновременно читать (и, таким образом, читать одно и то же значение), добавлять к нему одно, а затем оба записывают одно и то же значение, фактически добавляя только один раз. Interlocked.Add предотвращает это. Как правило, тот факт, что тип является атомарным, полезен только в том случае, если есть только один поток записи, но многие потоки читают.
@Larsenal, ваш ответ неоднозначен, да, это атомарно, и да, мне нужен замок?
Согласитесь с @ johnny5, ответ неоднозначный. Также предложение понять ключевое слово volatile неверно в отношении проблем синхронизации, поскольку volatile сам по себе не создает барьер памяти.
Доступ к bool действительно атомарен, но это еще не все.
Вам не нужно беспокоиться о чтении значения, которое `` не полностью написано '' - в любом случае не ясно, что это может означать для bool - но вам нужно беспокоиться о кешах процессора, по крайней мере, если детали время является проблемой. Если поток № 1, работающий на ядре A, имеет ваш _bar в кеше, а _bar обновляется потоком № 2, запущенным на другом ядре, поток № 1 не увидит изменения немедленно, если вы не добавите блокировку, не объявите _bar как volatile или явно не вставите вызовы в Thread.MemoryBarrier(), чтобы сделать кешированное значение недействительным.
«в любом случае неясно, что это могло означать для логического типа». Элементы, которые существуют только в одном байте атомарной памяти, потому что весь байт записывается в одно и то же время. По сравнению с такими элементами, как double, которые существуют в нескольких байтах, один байт может быть записан перед другим байтом, и вы можете наблюдать наполовину записанную ячейку памяти.
MemoryBarrier () не делает недействительным кэш процессора. В некоторых архитектурах процессору разрешено переупорядочивать операции чтения и записи в основную память для повышения производительности. Переупорядочивание может происходить до тех пор, пока с точки зрения одного потока семантика остается неизменной. MemoryBarrier () запрашивает у процессора ограничение переупорядочения, чтобы операции с памятью, выполненные до барьера, не переупорядочивались таким образом, чтобы они заканчивались после барьера.
Барьер памяти полезен, если вы создаете толстый объект и переключаете ссылку на него, которая может быть прочитана из других потоков. Барьер гарантирует, что ссылка не обновляется в основной памяти раньше, чем остальная часть жирного объекта. Гарантируется, что другие потоки никогда не увидят обновление ссылки до того, как толстый объект фактически станет доступным в основной памяти. var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;
Как указано выше, bool является атомарным, но вам все равно нужно помнить, что он также зависит от того, что вы хотите с ним делать.
if (b == false)
{
//do something
}
не атомарная операция, означающая, что значение b может измениться до того, как текущий поток выполнит код после оператора if.
подход, который я использовал и считаю правильным,
volatile bool b = false;
.. rarely signal an update with a large state change...
lock b_lock
{
b = true;
//other;
}
... another thread ...
if (b)
{
lock b_lock
{
if (b)
{
//other stuff
b = false;
}
}
}
Основная цель заключалась в том, чтобы избежать многократной блокировки объекта на каждой итерации, чтобы проверить, нужно ли блокировать его, чтобы предоставить большой объем информации об изменении состояния, что происходит редко. У меня считать этот подход работает. И если требуется абсолютная согласованность, для b bool будет уместным I считать volatile.
Это действительно правильный подход к блокировке в целом, но если bools атомарны, то проще (и быстрее) опустить блокировку.
Без блокировки «большое изменение состояния» не будет происходить атомарно. Замок -> установить | Подход check -> lock -> check также гарантирует, что код «// other» выполняется ПЕРЕД кодом «// other stuff». Предполагая, что раздел «другой поток» повторяется много раз (что и происходит в моем случае), большую часть времени нужно только проверять логическое значение, но не получать (возможно, конкурирующую) блокировку, это серьезный выигрыш в производительности.
Что ж, если у вас lock(), вам не нужен volatile.