Is a bool read/write atomic in C#
Asked Answered
C

4

97

Is accessing a bool field atomic in C#? In particular, do I need to put a lock around:

class Foo
{
   private bool _bar;

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

   //... same for a read
   if (_bar) { ... }
}
Canonry answered 12/9, 2008 at 16:19 Comment(2)
This earlier question seems related and may have additional useful answers.Xray
Yes, but (possibly) also yes. Yes accessing/setting a bool field is atomic, BUT the if operation is not (refer to Dror Helper's answer below) so you may still need a lock as well.Kruse
Q
139

Yes.

Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types.

as found in C# Language Spec.

Edit: It's probably also worthwhile understanding the volatile keyword.

Quinta answered 12/9, 2008 at 16:24 Comment(10)
Wait a second... reads and writes to reference types (e.g. any Object) is atomic?Cleavage
The pointer itself, reassigning it, is atomic (i.e. Foo foo1 = foo2;Tonnage
In my experience volatile is generally not a great idea: if you need volatile, you generally care about "simultaneous" actions - and even with a volatile, those are quite easy to get wrong. For instance the above if statement would require quite a bit of extra plumbing if it's to avoid many threadings entering - and if the aim is only to avoid entering until some thread has passed _bar = true; at least once, not using volatile at worst means that the if statement won't be take temporarily even though _bar is true. I'd suggest to just use locks if precision is required.Bostwick
@Eamon: If the case is that the if shouldn't be entered until a thread has set _bar = true;, then volatile is exactly what you want - why would you need locks here?Distressed
@configurator: The question is what exactly do you want. It's easy to get lock-free programs wrong; so unless you really need it, it's better to use a simpler framework (e.g. the TPL). 'volatile' is in other words not wrong, but a sign of tricky (i.e. preferably avoided) code. The OP hasn't really said what he wants, I'm just hesitant to recommend volatile willy-nilly.Bostwick
Waw. This is a dangerous wording, for C++ people atomic means that any read write are also surrounded by corresponding memory fence. Which is surely not the case in C#. Because otherwise the performance would be horrible since its mandatory for all variables < long. Atomic here in C# parlance, seems to mean than when the reads or writes eventually happen, they are guaranteed to never be in a broken state. But it says nothing as to when is "eventually".Dimarco
If writing to int and long are atomic, then when use Interlocked.Add(ref myInt); e.g.?Nuremberg
@MikedeKlerk The read and write are atomic, but seperate. i++ is equal to i=i+1, meaning you do an atomic read, then addition, then atomic write. Another thread could modify i after the read but before the write. For example two threads doing i++ concurrently on the same i can happen to read at the same time (and thus read the same value), add one to it and then both write the same value, effectively adding only once. Interlocked.Add prevents this. As a general rule the fact that a type is atomic is only useful if there's only one thread writing but many threads reading.Presignify
@Larsenal, you're answer in ambigous, Yes, this is atomic, and Yes I need a lock?Guitarist
Agree with @johnny5, the answer is ambiguous. Also suggesting to understand volatile keyword is wrong with regarding to synchronization problems as volatile alone does not put memory barrier.Hau
C
52

As stated above, bool is atomic, but you still need to remember that it also depends on what you want to do with it.

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

is not an atomic operation, meaning that the value of b could change before the current thread executes the code after the if statement.

Coincidentally answered 19/9, 2008 at 23:7 Comment(0)
R
33

bool accesses are indeed atomic, but that isn't the whole story.

You don't have to worry about reading a value that is 'incompletely written' - it isn't clear what that could possibly mean for a bool in any case - but you do have to worry about processor caches, at least if details of timing are an issue. If thread #1 running on core A has your _bar in cache, and _bar gets updated by thread #2 running on another core, thread #1 will not see the change immediately unless you add locking, declare _bar as volatile, or explicitly insert calls to Thread.MemoryBarrier() to invalidate the cached value.

Reductase answered 12/9, 2008 at 16:53 Comment(3)
"it isn't clear what that could possibly mean for a bool in any case " Items that exist in only one byte of memory at atomic because the entire byte is written to at the same time. Versus items like double which exist in multiple bytes, one byte could be written before the other byte and you could observe a half written memory location.Brutish
MemoryBarrier() does not invalidate any processor cache. In some architecture, the processor is allowed to reorder reads and writes to main memory for performance. Reordering may happen so long as from the point of view of a single thread, the semantics stay the same. MemoryBarrier() requests the processor to limit the reordering so that memory operations emitted before the barrier are not reordered in such a way that they end up after the barrier.Kamacite
A memory barrier is useful if you create a fat object and switch a reference to it that may be read from other threads. The barrier guarantees the reference is not updated in main memory before the rest of the fat object. Other threads are guaranteed to never see the reference update before the fat object is actually available in main memory. var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;Kamacite
C
3

the approach I have used, and I think is correct, is

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

the goal was basically to avoid having to repetively lock an object on every iteration just to check if we needed to lock it in order to provide a large amount of state change information which occurs rarely. I think this approach works. And if absolute consistancy is required, I think volatile would be appropriate on the b bool.

Curmudgeon answered 21/5, 2013 at 7:52 Comment(3)
This is indeed a correct approach to locking in general, but if bools are atomic, then it's simpler (and faster) to omit the lock.Canonry
Without the lock then the "large state change" will not be done atomically. The lock -> set | check -> lock -> check approach will also ensure that the "//other" code is executed BEFORE the "//other stuff" code. Assuming the "another thread" section is iterating many times (which it is in my case), only having to check a bool, most of the time, but not actually acquire a (possibly contended) lock, is a major performance winCurmudgeon
Well, if you have lock(), you do not need volatile.Panache

© 2022 - 2024 — McMap. All rights reserved.