Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do I need to lock or mark as volatile when accessing a simple boolean flag in C#?

Lets just say you have a simple operation that runs on a background thread. You want to provide a way to cancel this operation so you create a boolean flag that you set to true from the click event handler of a cancel button.

private bool _cancelled;  private void CancelButton_Click(Object sender ClickEventArgs e) {     _cancelled = true; } 

Now you're setting the cancel flag from the GUI thread, but you're reading it from the background thread. Do you need to lock before accessing the bool?

Would you need to do this (and obviously lock in the button click event handler too):

while(operationNotComplete) {     // Do complex operation      lock(_lockObject)     {         if(_cancelled)         {             break;         }     } } 

Or is it acceptable to do this (with no lock):

while(!_cancelled & operationNotComplete) {     // Do complex operation } 

Or what about marking the _cancelled variable as volatile. Is that necessary?

[I know there is the BackgroundWorker class with it's inbuilt CancelAsync() method, but I'm interested in the semantics and use of locking and threaded variable access here, not the specific implementation, the code is just an example.]

There seems to be two theories.

1) Because it is a simple inbuilt type (and access to inbuilt types is atomic in .net) and because we are only writing to it in one place and only reading on the background thread there is no need to lock or mark as volatile.
2) You should mark it as volatile because if you don't the compiler may optimise out the read in the while loop because it thinks nothing it capable of modifying the value.

Which is the correct technique? (And why?)

[Edit: There seem to be two clearly defined and opposing schools of thought on this. I am looking for a definitive answer on this so please if possible post your reasons and cite your sources along with your answer.]

like image 236
Simon P Stevens Avatar asked Aug 03 '09 12:08

Simon P Stevens


People also ask

In what case would you declare a field as volatile?

The volatile keyword indicates that a field might be modified by multiple threads that are executing at the same time. The compiler, the runtime system, and even hardware may rearrange reads and writes to memory locations for performance reasons.

How to use volatile in c#?

To declare a variable volatile, just add volatile as one of the variable modifiers. In the following example, there are two static fields one field is a object reference of string type, and the other is a volatile bool value type. In the Main method, a new thread is created, and the SetVolatile method is invoked.


2 Answers

Firstly, threading is tricky ;-p

Yes, despite all the rumours to the contrary, it is required to either use lock or volatile (but not both) when accessing a bool from multiple threads.

For simple types and access such as an exit flag (bool), then volatile is sufficient - this ensures that threads don't cache the value in their registers (meaning: one of the threads never sees updates).

For larger values (where atomicity is an issue), or where you want to synchronize a sequence of operations (a typical example being "if not exists and add" dictionary access), a lock is more versatile. This acts as a memory-barrier, so still gives you the thread safety, but provides other features such as pulse/wait. Note that you shouldn't use a lock on a value-type or a string; nor Type or this; the best option is to have your own locking object as a field (readonly object syncLock = new object();) and lock on this.

For an example of how badly it breaks (i.e. looping forever) if you don't synchronize - see here.

To span multiple programs, an OS primitive like a Mutex or *ResetEvent may also be useful, but this is overkill for a single exe.

like image 192
Marc Gravell Avatar answered Sep 22 '22 06:09

Marc Gravell


_cancelled must be volatile. (if you don't choose to lock)

If one thread changes the value of _cancelled, other threads might not see the updated result.

Also, I think the read/write operations of _cancelled are atomic:

Section 12.6.6 of the CLI spec states: "A conforming CLI shall guarantee that read and write access to properly aligned memory locations no larger than the native word size is atomic when all the write accesses to a location are the same size."

like image 42
bruno conde Avatar answered Sep 21 '22 06:09

bruno conde