Search code examples
c#multithreadingvolatile

acquire & release semantics implied during a lock?


The volatile keyword is used to protect fields from certain compiler optimizations:

For non-volatile fields, optimization techniques that reorder instructions can lead to unexpected and unpredictable results in multi-threaded programs that access fields without synchronization such as that provided by the lock-statement (Section 8.12)

However, MSDN doesn't seem to make clear whether lock applies optimization protections to just the object used in the expression, or all statements in the statement_block.

If I have a block of code:

lock(obj_something){
    boolFoo = true;
    if(boolIsFriday)
        doSomething(anything);
}

Is boolFoo and boolIsFriday implicity volatile (even if it was not declared volatile)?


Solution

  • This is a difficult topic and I suggest you start by reading Joe Duffy's book Concurrent Programming on Windows - it's 1000 pages of pure knowledge.

    In answer to your question: no the variables are not made "implicitly volatile"

    What the lock does provide - apart from mutual exclusion - is a fence at both acquire and release.

    This means some restrictions are put on what re-ordering optimizations can be applied by the compiler, jitter, cpu or anything in between.

    In particular:

    Acquiring the lock prevents any memory accesses from moving before the fence.
    Releasing the lock prevents any memory accesses from moving after the fence.


    In your example, the write to boolFoo cannot be observed by any thread before the lock is acquired.

    Similarly the reads of boolIsFriday and anything cannot use values that were read before the lock was acquired.

    At release, the write to boolFoo must be visible when the lock is released, as must any writes performed by the doSomething method.


    In answer to your comment: this doesn't prevent other threads from re-ordering.

    If you have another thread B that does this:

    // do some work while your lock code is running
    for ( int i = 0 ; i < ( int ) 1e7 ; i++ ) ;
    
    var copyOfBoolFoo = boolFoo;
    

    then it is possible for copyOfBoolFoo to be false.

    That is because something might look ahead, before the for loop and your lock code runs, and decide to read boolFoo and cache the value.

    There isn't anything in thread B to prevent this, so it may happen.

    If you put a fence ( e.g. a lock! ) before the read of boolFoo in thread B, then you would guarantee to read the latest value.