C # Based on the review: volatile keyword

Some people may have never seen this keyword, it's no wonder, because the keyword is not commonly used. That this keyword in the end what use is it?

I was online keyword search, I found many of my friends have a wrong perception ------ think this keyword prevents concurrent contention (somewhat similar to the lock catch the foot).

 

Redefine the role of volatile

volatile Chinese explanation is "variable", on MSDN explanation of this keyword as follows: "volatile keyword indicates thread modifies a field that can be executed simultaneously by multiple fields are not declared as volatile compiler optimization (assumed by the. a single thread access) restrictions. this ensures that all the latest value of the field at any time of presentation. "

I do not know you read the above description is not understood why, anyway, I did not understand. After numerous online access to information, be considered to be understood, after reading the above, then re-used in new ways, there is the following conclusions.

 

1, to prevent the compiler optimization: JIT compiler will automatically optimize the code, leading to a change in the final code sequence of instructions. Use the volatile keyword can be avoided JIT compiler optimized for this, such as:

public bool _goOn = true;
 
//未优化
public void Execute()
{
    while(_goOn)
    {
        //do something
    }
}
 
//优化后
public void ExecuteOptimized
{
    if(_goOn)
    {
        while(true)
        {
            //do something
        }
    }
}

 The above method is only used to give an example, the case where the actual optimization not exactly the same.

Because JIT believes, _goOn the value of the variable is not modified in the cycle within a single thread, there is no need to re-read every time, and therefore will be to extract this value. But if at the time of the cycle, there is another thread changes the value of _goOn, it would be logical error.

volatile keyword in C ++ can not guarantee the order of execution of instructions

 

2, to prevent Processor Optimization: especially for multi-threaded multi-core CPU, when the two threads operate with a variable one constantly reads this variable, the other continues to change this variable in order to reduce CPU will a large number of memory accesses, and this variable cache cache multiple cores, so each execution of instructions can be quickly returned from the cache (cache access speed is much higher than the access speed of the memory). Although this improves performance, but the accompanying problem is that one thread can not receive another thread updates the variables at once. Use the volatile keyword ensures that each of the variables reads and updates are direct operating memory, which means that each thread of the acquired values ​​are the same, there will be no conflict.

 

volatile operating results

In  Stackoverflow  on, a friend gives a volatile instance can run this example by the action can be more intuitive knowledge of volatile.

static void Main()
{
    var test = new Test();
 
    new Thread(delegate() { Thread.Sleep(500); test.foo = 255; }).Start();
 
    while (true)
    {
        if (test.foo == 255)
        {
            break;
        }
    };
    Console.WriteLine("OK");
}

 

According to a friend who runs the program given me in release mode, use Ctrl + F5 to run directly output was:

Many wait, still no output

 

Modify foo modifiers, coupled with volatile, then run:

 

The machine was operating environment: Win7 x64, Visual Studio 2012.

 

之所以在不用 volatile 关键字修饰的时候会导致死循环,就是因为指令被优化了。不同的 CPU 架构采用的方式会有所不同,在我的机器上(x64)上,通过查看运行时的汇编指令时可以发现在没有使用 volatile 的情况下,在判断 test.foo == 255 这句话的时候,一直是在读取 EAX 寄存器中的值。而当使用了 volatile 关键字后,每次都是重新从内在中读取。

// 没有使用 volatile 的情况
0000004f  mov         eax,dword ptr [esi+4]    // 读取内存中的值,并保存在寄存器 EAX 中(esi 指向内存中的地址)
00000052  mov         eax,dword ptr [eax+4] 
00000055  cmp         eax,0FFh                 // 直接比较寄存器 EAX 的值是否为 255
0000005a  jne         00000055                 // 如果判断不成立,则继续执行上一行代码
 
 
// 使用了 volatile 的情况
0000004f  mov         eax,dword ptr [esi+4]     // 读取内存中的值,并保存在寄存器 EAX 中 
00000052  cmp         dword ptr [eax+4],0FFh    // 比较寄存器 EAX 的值是否为 255
00000059  jne         0000004F                  // 如果判断不成立,则继续执行地址为 4f 的代码

 当没有 volatile 修饰时,执行循环的线程只读取了一次 foo 值,然后一直使用该值,造成了死循环。而使用 volatile 后,每次都会去查看最新的 foo 值,因此才能正常执行。

寄存器知识拾遗:多核 CPU 中,每个核心都有全套寄存器。一个线程只可能在一个核心上运行,不可能开始的时候在核心 A 上,结束时却在核心 B 上,这意味着一个线程在其生命周期内只可能操作一套寄存器。而当同一个核心上的不同线程切换时,当前CPU的寄存器值会被保存到线程内核对象的一个上下文结构中,然后下次该线程被再次调度时,会用内核对象中保存的值恢复寄存器。

volatile 不能替代 lock

从上述提到的两点,应该不难看出 volatile 关键字的作用中并没有哪一点是用于避免多线程对同一个变量的争用的,也就是说它不具有同步的作用。

先来看一个示例:

static int i = 0;
 
static void Main(string[] args)
{
    Task t = Task.Factory.StartNew(() =>
    {
        i = 10; 
        //Thread.Sleep(500);
        Console.WriteLine("10 i={0}", i);
    });
    Task t2 = Task.Factory.StartNew(() =>
    {
        i = 100;
        //Thread.Sleep(1);
        Console.WriteLine("100 i={0}", i);
    });
         
    Console.ReadLine();
}


10 i=100上述程序运行后,除了主线程,还会创建两个新线程,且都会修改同一个变量。由于无法控制每个线程执行的时机,上述代码运行的结果有可能如下(把注释掉的代码反注释回来,效果更明显):

100 i=100

 

这就需要同步机制。修改上述代码,加上 lock 看下效果:

static object lckObj = new object();
static int i = 0;
 
static void Main(string[] args)
{
 
    Task t = Task.Factory.StartNew(() =>
    {
        lock (lckObj)
        {
            i = 10;
            //Thread.Sleep(500);
            Console.WriteLine("10 Thread.Id:{0} i={1}", Thread.CurrentThread.ManagedThreadId, i);
        }
    });
    Task t2 = Task.Factory.StartNew(() =>
    {
        lock (lckObj)
        {
            i = 100;
            //Thread.Sleep(1);
            Console.WriteLine("100 Thread.Id:{0} i={1}", Thread.CurrentThread.ManagedThreadId, i);
        }
    });
         
    Console.ReadLine();
}


10 i=10现在,无论运行上述代码多少次,得的答案都是一样的:

100 i=100

 

现在,再使用 volatile 看看,是否有同步的效果:

static volatile int i = 0;
 
static void Main(string[] args)
{
    Task t = Task.Factory.StartNew(() =>
    {
        i = 10; 
        //Thread.Sleep(500);
        Console.WriteLine("10 i={0}", i);
    });
    Task t2 = Task.Factory.StartNew(() =>
    {
        i = 100;
        //Thread.Sleep(1);
        Console.WriteLine("100 i={0}", i);
    });
         
    Console.ReadLine();
}


 运行后,你便会发现,屏幕上显示的输出和没有使用 lock 是完全一样的。

什么时候使用 volatile?

x86 和 x64 架构的 CPU 本身已经对指令的顺序进行了严格的约束,除了各别情况,大多数情况下使用和不使用 volatile 的效果是一样的。

As it happens, Intel’s X86 and X64 processors always apply acquire-fences to reads and release-fences to writes — whether or not you use the volatile keyword — so this keyword has no effect on the hardware if you’re using these processors. However, volatile does have an effect on optimizations performed by the compiler and the CLR — as well as on 64-bit AMD and (to a greater extent) Itanium processors. This means that you cannot be more relaxed by virtue of your clients running a particular type of CPU.

Nonblocking Synchronization

上面的文字大致意思是指 X86 和 X64 的处理器总是会加入内存屏障来防止乱序,所以加不加 volatile 效果一样。但是在诸如 64位的 AMD CPU 或者 Itanium CPU 则需要手动去预防可能的乱序。

 

lock 关键字会隐式提供内存屏障,且更严格(完全禁止乱序和缓存,而 volatile 只是禁止一部分的乱序,这样编译器仍然可以在一定程度上进行代码优化),在性能上要差于 volatile。因此,除非你非常在意性能,同时对内存模型或CPU平台非常了解,否则建议直接使用 lock 关键字,lock 关键字不止屏蔽了乱序和缓存可能引起的异常,同时也可以避免多个线程的争用。

The following implicitly generate full fences: C#'s lock statement (Monitor.Enter/Monitor.Exit)、All methods on the Interlocked class (we’ll cover these soon) ...

Nonblocking Synchronization

volatile is used to create a memory barrier* between reads and writes on the variable. lock, when used, causes memory barriers to be created around the block inside the lock, in addition to limiting access to the block to one thread.

  --- Stackoverflow

 

修改 <volatile 运行效果> 这一节中的示例,使用 lock 关键字,如:

int foo;
    static object lckObj = new object();
 
    static void Main()
    {
        var test = new Program();
 
        new Thread(delegate()
        {
            Thread.Sleep(500);
            lock (lckObj)
                test.foo = 255;
        }).Start();
 
        while (true)
        {
            lock (lckObj)
                if (test.foo == 255)
                {
                    break;
                }
        }
        Console.WriteLine("OK");
    }

 The code uses the operating results and the effect of the volatile keyword the same.

Resources

Don't get C# volatile the wrong way

Volatile fields in .NET: A look inside

Volatile vs. Interlocked vs. lock

Nonblocking Synchronization

C / C ++ Volatile depth keyword analysis

Reprinted to  http://blog.chenxu.me/post/detail?id=1d39c8ae-4ed7-4498-8408-9ef3a71ed954

Guess you like

Origin www.cnblogs.com/jshchg/p/11672209.html