18

I read recently about memory barriers and the reordering issue and now I have some confusion about it.

Consider the following scenario:

private object _object1 = null;    
private object _object2 = null;
private bool _usingObject1 = false;

private object MyObject
{
    get 
    {
        if (_usingObject1)
        {
            return _object1;
        }
        else
        {
            return _object2;
        }
    }
    set 
    {
        if (_usingObject1)
        {
           _object1 = value;
        }
        else
        {
           _object2 = value;
        }
    }
}

private void Update()
{
    _usingMethod1 = true;
    SomeProperty = FooMethod();
    //..
    _usingMethod1 = false;
}
  1. At Update method; is the _usingMethod1 = true statement always executed before getting or setting the property? or due to reordering issue we can not guarantee that?

  2. Should we use volatile like

    private volatile bool _usingMethod1 = false;
    
  3. If we use lock; can we guarantee then every statement within the lock will be executed in order like:

    private void FooMethod()
    {
        object locker = new object();
        lock (locker)
        {
            x = 1;
            y = a;
            i++;
        }
    }
    
trooper
  • 4,444
  • 5
  • 32
  • 32
Jalal Said
  • 15,906
  • 7
  • 45
  • 68

2 Answers2

35

The subject of memory barriers is quite complex. It even trips up the experts from time to time. When we talk about a memory barrier we are really combining two different ideas.

  • Acquire fence: A memory barrier in which other reads & writes are not allowed to move before the fence.
  • Release fence: A memory barrier in which other reads & writes are not allowed to move after the fence.

A memory barrier that creates only one of two is sometimes called a half-fence. A memory barrier that creates both is sometimes called a full-fence.

The volatile keyword creates half-fences. Reads of volatile fields have acquire semantics while writes have release semantics. That means no instruction can be moved before a read or after a write.

The lock keyword creates full-fences on both boundaries (entry and exit). That means no instruction can be moved either before or after each boundary.

However, all of this moot if we are only concerned with one thread. Ordering, as it is perceived by that thread, is always preserved. In fact, without that fundamental guarentee no program would ever work right. The real issue is with how other threads perceive reads and writes. That is where you need to be concerned.

So to answer your questions:

  1. From a single thread's perspective...yes. From another thread's perspective...no.

  2. It depends. That might work, but I need to have better understanding of what you are trying to acheive.

  3. From another thread's perspective...no. The reads and writes are free to move around within the boundaries of the lock. They just cannot move outside those boundaries. That is why it is important for other threads to also create memory barriers.

Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
  • Thanks for the information, It really helps me understanding the concept more.. What I need to achive is to make sure the "_usingMethod1 = true" instruction will alwayes done before the next instruction SomeProperty = FooMethod(); In multithread senario how to acomplish that? is by: _usingMethod1 = true; Thread.MemoryBarrier(); SomeProperty = FooMethod(); or lock for full fences so no reordaring: lock (locker) { _usingMethod1= true; } SomeProperty = FooMethod(); or maybe just by making the _usingMethod1 a volatile variable. Thanks for your help. – Jalal Said May 17 '10 at 08:59
  • 4
    I would wrap the entire contents of the Update method in a lock. In addition to the memory barriers it also guarentees atomicity, which is equally important. Besides, these lock-free idioms (via volatile, Thread.MemoryBarrier, etc.) are incredibly difficult to get right. – Brian Gideon May 17 '10 at 13:25
  • Your definitions of acquire and release fence are not exactly correct. Acquire also prevents previous reads to move after the fence and release prevents subsequent writes to move before it. Otherwise, you could move everything before acquire or after release over the fence and the fence wouldn't provide any guarantees at all. – relatively_random Oct 21 '20 at 08:15
  • Another subtlety - volatile doesn't guarantee to produce actual acquire/release fences. Volatile read guarantees that all subsequent memory accesses will not be reordered before just that read, but any previous reads to other variables are allowed to move below the volatile read. An acquire fence would also prevent all previous reads to move below any subsequent memory access. It's similar for volatile writes - it's not a release fence, just a release write. – relatively_random Oct 21 '20 at 08:19
  • Finally, AFAIK, locks only require acquire *semantics* on enter and release *semantics* on exit - not even acquire/release fences. In other words, they just need to keep the operations inside the block from escaping, but don't need to affect anything outside it. In theory, an unrelated memory access that happened before the lock block could be reordered after it or vice-versa. – relatively_random Oct 21 '20 at 08:41
4

The volatile keyword doesn't accomplish anything here. It has very weak guarantees, it does not imply a memory barrier. Your code doesn't show another thread getting created so it is hard to guess if locking is required. It is however a hard requirement if two threads can execute Update() at the same time and use the same object.

Beware that your lock code as posted doesn't lock anything. Each thread would have its own instance of the "locker" object. You have to make it a private field of your class, created by the constructor or an initializer. Thus:

private object locker = new object();

private void Update()
{
    lock (locker)
    {
        _usingMethod1 = true;
        SomeProperty = FooMethod();
        //..
        _usingMethod1 = false;
    }
}

Note that there will also be a race on the SomeProperty assignment.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • The volatile has a memory barrier; and so I asked if that momory barrier with it will suppress the reordaring so the _usingMethod1 = true will always garanteed to excute before getting or setting the property SomeProperty, I provide a lock just for memorry barrier not for syncronization issue with other threads and so I make it a local variable within method in purpose because I was asking if it will avoid the reordaring of instructions inside the lock. – Jalal Said May 17 '10 at 08:31
  • 1
    A single thread *always* has a consistent view of the variables it uses. Programs could not work if that wasn't the case. – Hans Passant May 17 '10 at 10:20