Volatile and Thread.MemoryBarrier in C#

最后都变了- 提交于 2019-11-30 13:01:52

The "C# In a Nutshell" is correct, but its statement is moot. Why?

  • A 'write' followed by a 'read', withot 'volatile', is guaranteed to occur in program-order anyway if it effects logic within a single thread
  • The 'write' before a 'read' in a multi-threaded program is utterly pointless to worry about in your example.

Let's clarify. Take your original code:

private void A() 
{ 
    //… 
    if (_foo) 
    { 
        //do something 
    } 
}

What happens if the thread scheduler has already checked the _foo variable, but it gets suspended just before the //do something comment? Well, at that point your other thread could change the value of _foo, which means that all your volatiles and Thread.MemoryBarriers counted for nothing!!! If it is absolutely essential that the do_something be avoided if the value of _foo is false, then you have no choice but to use a lock.

However, if it is ok for the do something to be executing when suddenly _foo becomes false, then it means the volatile keyword was more than enough for your needs.

To be clear: all the responders who are telling you to use a memory barrier are incorrect or are providing overkill.

The book is correct.
The CLR's memory model indicates that load and store operations may be reordered. This goes for volatile and non-volatile variables.

Declaring a variable as volatile only means that load operations will have acquire semantics, and store operations will have release semantics. Also, the compiler will avoid performing certain optimizations that relay on the fact that the variable is accessed in a serialized, single-threaded fashion (e.g. hoisting load/stores out of loops).

Using the volatile keyword alone doesn't create critical sections, and it doesn't cause threads to magically synchronize with each other.

You should be extremely careful when you write lock free code. There's nothing simple about it, and even the experts have trouble to get it right.
Whatever is the original problem you're trying to solve, it's likely that there's a much more reasonable way to do it.

In your second example, you would need to also put a Thread.MemoryBarrier(); inside the loop, to make sure you get the most recent value every time you check the loop condition.

Pulled from here...

class Foo
{
  int _answer;
  bool _complete;

  void A()
  {
    _answer = 123;
    Thread.MemoryBarrier();    // Barrier 1
    _complete = true;
    Thread.MemoryBarrier();    // Barrier 2
  }

  void B()
  {
    Thread.MemoryBarrier();    // Barrier 3
    if (_complete)
    {
      Thread.MemoryBarrier();       // Barrier 4
      Console.WriteLine (_answer);
    }
  }
}

Barriers 1 and 4 prevent this example from writing “0”. Barriers 2 and 3 provide a freshness guarantee: they ensure that if B ran after A, reading _complete would evaluate to true.

So if we go back to your looping example...this is how it should look...

private void A()
{
    Thread.MemoryBarrier();
    while (_someOtherConditions && _foo)
    {
        //do somthing
        Thread.MemoryBarrier();
    }
}

Microsoft's own words on memory barriers:

MemoryBarrier is required only on multiprocessor systems with weak memory ordering (for example, a system employing multiple Intel Itanium processors).

For most purposes, the C# lock statement, the Visual Basic SyncLock statement, or the Monitor class provide easier ways to synchronize data.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!