Атомарное чтение / запись 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) { ... }
}


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()для аннулирования кешированное значение.


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.


4
Это действительно правильный подход к блокировке в целом, но если bool атомарны, то проще (и быстрее) опустить блокировку.
— dbkk

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

1
Ну а если есть lock(), то и не надо volatile.
— xmedeko 07
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.