Атомарное чтение / запись bool в C #

84

Является ли доступ к полю bool атомарным в C #? В частности, мне нужно поставить блокировку:

class Foo
{
   private bool _bar;

   //... in some function on any thread (or many threads)
   _bar = true;

   //... same for a read
   if (_bar) { ... }
}
dbkk
источник
1
Да, но (возможно) тоже да. Да, доступ / установка поля bool является атомарным, НО операция if - нет (см. Ответ Dror Helper ниже), поэтому вам все равно может понадобиться блокировка.
JPProgrammer

Ответы:

119

Да.

Чтение и запись следующих типов данных являются атомарными: bool, char, byte, sbyte, short, ushort, uint, int, float и ссылочные типы.

как указано в Спецификации языка C # .

Изменить: вероятно, также стоит понять ключевое слово volatile .

Ларсенал
источник
10
Сам указатель, переназначающий его, является атомарным (т.е. Foo foo1 = foo2;
user142350,
4
@configurator: Вопрос в том, что именно вам нужно. Легко ошибиться в программах без блокировки; поэтому, если вам это действительно не нужно, лучше использовать более простой фреймворк (например, TPL). «volatile», другими словами, не является неправильным, а является признаком хитрого (т.е. желательно избегать) кода. ОП на самом деле не сказал, что он хочет, я просто не решаюсь рекомендовать изменчивый волей-неволей.
Eamon Nerbonne
4
Вау. Это опасная формулировка, поскольку атомарность для C ++ означает, что любое чтение и запись также окружены соответствующим забором памяти. Что, конечно, не так в C #. Потому что в противном случае производительность была бы ужасной, поскольку она обязательна для всех переменных <long. Атомарный здесь, на языке C #, кажется, означает, что когда в конечном итоге происходит чтение или запись, они гарантированно никогда не будут в неисправном состоянии . Но он ничего не говорит о том, когда наступит «в конце концов».
v.oddou
4
Если запись в int и long является атомарной, то при использовании, Interlocked.Add(ref myInt);например,?
Майк де Клерк
6
@MikedeKlerk Чтение и запись атомарны, но разделены. i++равно i=i+1, что означает, что вы выполняете атомарное чтение, затем сложение, а затем атомарную запись. Другой поток может измениться iпосле чтения, но до записи. Например, два потока, выполняющие i++одновременно один и тот же i, могут одновременно читать (и, таким образом, читать одно и то же значение), добавлять к нему одно, а затем оба записывать одно и то же значение, фактически добавляя только один раз. Interlocked.Add предотвращает это. Как правило, тот факт, что тип является атомарным, полезен только в том случае, если есть только один поток записи, но много потоков чтения.
Jannis Froese
49

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

if(b == false)
{
    //do something
}

не является атомарной операцией, что означает, что значение b может измениться до того, как текущий поток выполнит код после оператора if.

Дрор Помощник
источник
30

Доступ к bool действительно атомарен, но это еще не все.

Вам не нужно беспокоиться о чтении значения, которое `` не полностью написано '' - в любом случае не ясно, что это может означать для bool - но вам нужно беспокоиться о кешах процессора, по крайней мере, если детали время является проблемой. Если поток № 1, работающий на ядре A, имеет ваш _barв кеше и _barобновляется потоком № 2, запущенным на другом ядре, поток № 1 не увидит изменения сразу, если вы не добавите блокировку, не объявите _barкак volatileили явно не вставите вызовы Thread.MemoryBarrier()для аннулирования кешированное значение.

McKenzieG1
источник
1
«в любом случае не ясно, что это могло означать для логического значения». Элементы, которые существуют только в одном байте атомарной памяти, потому что весь байт записывается одновременно. По сравнению с такими элементами, как double, которые существуют в нескольких байтах, один байт может быть записан перед другим байтом, и вы можете наблюдать наполовину записанную ячейку памяти.
MindStalker 01
3
MemoryBarrier () не делает недействительным кеш процессора. В некоторых архитектурах процессору разрешено изменять порядок операций чтения и записи в основную память для повышения производительности. Изменение порядка может происходить до тех пор, пока с точки зрения одного потока семантика остается неизменной. MemoryBarrier () запрашивает у процессора ограничение переупорядочения, чтобы операции с памятью, выполненные до барьера, не переупорядочивались таким образом, чтобы они заканчивались после барьера.
TiMoch 03
1
Барьер памяти полезен, если вы создаете толстый объект и переключаете ссылку на него, которая может быть прочитана из других потоков. Барьер гарантирует, что ссылка не обновляется в основной памяти раньше, чем остальная часть жирного объекта. Гарантируется, что другие потоки никогда не увидят обновление ссылки до того, как толстый объект фактически станет доступным в основной памяти. var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;
TiMoch 03
1

подход, который я использовал и считаю правильным,

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;
       }
     }
}

Основная цель заключалась в том, чтобы избежать многократной блокировки объекта на каждой итерации, чтобы проверить, нужно ли блокировать его, чтобы предоставить большой объем информации об изменении состояния, что происходит редко. Я думаю , что этот подход работает. И если требуется абсолютная согласованность, я думаю , что volatile будет уместным для b bool.

Stux
источник
4
Это действительно правильный подход к блокировке в целом, но если bool атомарны, то проще (и быстрее) опустить блокировку.
dbkk
2
Без блокировки «большое изменение состояния» не будет происходить атомарно. Замок -> установить | Подход check -> lock -> check также гарантирует, что код «// other» выполняется ДО кода «// other stuff». Предполагая, что раздел «другой поток» повторяется много раз (что и происходит в моем случае), большую часть времени приходится проверять только логическое значение, но не получать блокировку (возможно,
конкурирующую
1
Ну а если есть lock(), то и не надо volatile.
xmedeko 07