在锁语句内部是否仍需要volatile? [英] Is volatile still needed inside lock statements?

查看:68
本文介绍了在锁语句内部是否仍需要volatile?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在不同的地方读过,人们说人们应该始终使用 lock 而不是 volatile .我发现那里有很多关于多线程的令人困惑的陈述,甚至专家在这里对某些事情也有不同的看法.

I have read at different places people saying one should always use lock instead of volatile. I found that there are lots of confusing statements about Multithreading out there and even experts have different opinions on some things here.

经过大量研究,我发现lock语句还将至少插入 MemoryBarriers .

After lots of research I found that the lock statement also will insert MemoryBarriers at least.

例如:

    public bool stopFlag;

    void Foo()
    {
        lock (myLock)
        {
          while (!stopFlag)
          {
             // do something
          }
        }
    }

但是,如果我不是完全错误的话,那么JIT编译器可以自由地从未实际在循环内读取变量,而是只能从寄存器中读取变量的缓存版本.如果JIT对变量进行寄存器分配,则AFAIK MemoryBarriers不会起作用,它只是确保如果我们从内存中读取的值是最新的.

But if I am not totally wrong, the JIT compiler is free to never actually read the variable inside the loop but instead it may only read a cached version of the variable from a register. AFAIK MemoryBarriers won't help if the JIT made a register assignment to a variable, it just ensures that if we read from memory that the value is current.

除非有一些编译器魔术说过类似的话,例如如果代码块包含MemoryBarrier,则在MemoryBarrier被阻止之后对所有变量进行寄存器分配".

Unless there is some compiler magic saying something like "if a code block contains a MemoryBarrier, register assignment of all variables after the MemoryBarrier is prevented".

除非已声明 volatile 或使用 Thread.VolatileRead()进行读取(如果从另一个 Thread 设置了 myBool ),改为 false ,则循环可能仍会无限运行,这是正确的吗?如果是,这是否不适用于线程之间共享的所有变量?

Unless declared volatile or read with Thread.VolatileRead(), if myBool is set from another Thread to false, the loop may still run infinite, is this correct? If yes, wouldn't this apply to ALL variables shared between Threads?

推荐答案

每当我看到这样的问题时,我的下意识反应就是什么都不做!".NET内存模型非常薄弱,而C#内存模型对于使用仅适用于具有甚至不再受支持的弱内存模型的处理器的语言尤其值得注意.没有什么可以告诉您此代码中将发生的任何事情,您可以对锁和内存屏障进行推理,直到脸色发青,但是却一无所获.

Whenever I see a question like this, my knee-jerk reaction is "assume nothing!" The .NET memory model is quite weak and the C# memory model is especially noteworthy for using language that can only apply to a processor with a weak memory model that isn't even supported anymore. Nothing what's there tells you anything what's going to happen in this code, you can reason about locks and memory barriers until you're blue in the face but you don't get anywhere with it.

x64抖动非常干净,很少引起意外.但是它的日子已经过去了,它将在VS2015中被Ryujit取代.从x86抖动代码库作为起点开始的重写.令人担忧的是,x86抖动可能使您陷入困境.双关语.

The x64 jitter is quite clean and rarely throws a surprise. But its days are numbered, it is going to be replaced by Ryujit in VS2015. A rewrite that started with the x86 jitter codebase as a starting point. Which is a concern, the x86 jitter can throw you for a loop. Pun intended.

最好的办法就是尝试一下,看看会发生什么.稍微重写一下代码,并使该循环尽可能紧密,以便抖动优化器可以执行其所需的任何操作:

Best thing to do is to just try it and see what happens. Rewriting your code a little bit and making that loop as tight as possible so the jitter optimizer can do anything it wants:

class Test {
    public bool myBool;
    private static object myLock = new object();
    public int Foo() {
        lock (myLock) {
            int cnt = 0;
            while (!myBool) cnt++;
            return cnt;
        }
    }
}

并像这样测试它:

    static void Main(string[] args) {
        var obj = new Test();
        new Thread(() => {
            Thread.Sleep(1000);
            obj.myBool = true;
        }).Start();
        Console.WriteLine(obj.Foo());
    }

切换到发布版本.在项目+属性"的构建"选项卡上,选中首选32位"选项.在工具+选项",调试",常规"中,取消选中抑制JIT优化"选项.首先运行调试版本.工作正常,程序一秒钟后终止.现在切换到Release版本,运行并观察其死锁,循环永远不会完成.使用Debug + Break All可以看到它挂在循环中.

Switch to the Release build. Project + Properties, Build tab, tick the "Prefer 32-bit" option. Tools + Options, Debugging, General, untick the "Suppress JIT optimization" option. First run the Debug build. Works fine, program terminates after a second. Now switch to the Release build, run and observe that it deadlocks, the loop never completes. Use Debug + Break All to see that it hangs in the loop.

要了解原因,请使用Debug + Windows + Disassembly查看生成的机器代码.仅关注循环:

To see why, look at the generated machine code with Debug + Windows + Disassembly. Focusing on the loop only:

                int cnt = 0;
013E26DD  xor         edx,edx                      ; cnt = 0
                while (myBool) {
013E26DF  movzx       eax,byte ptr [esi+4]         ; load myBool 
013E26E3  test        eax,eax                      ; myBool == true?
013E26E5  jne         013E26EC                     ; yes => bail out
013E26E7  inc         edx                          ; cnt++
013E26E8  test        eax,eax                      ; myBool == true?
013E26EA  jne         013E26E7                     ; yes => loop
                }
                return cnt;

地址013E26E8上的指令讲述了这个故事.请注意 myBool 变量如何存储在edx寄存器的 eax 寄存器 cnt 中.抖动优化器的标准职责是使用处理器寄存器并避免内存加载和存储,从而使代码更快.并请注意,当测试该值时,它仍然使用寄存器,并且不从内存中重新加载 not .因此,此循环永远不会结束,并且将始终挂起您的程序.

The instruction at address 013E26E8 tells the tale. Note how the myBool variable is stored in the eax register, cnt in the edx register. A standard duty of the jitter optimizer, using the processor registers and avoiding memory loads and stores makes the code much faster. And note that when it tests the value, it still uses the register and does not reload from memory. This loop can therefore never end and it will always hang your program.

代码当然是伪造的,没有人会写这个.实际上,这往往是偶然的,您可以在while()循环中添加更多代码.太多的抖动使抖动无法完全优化可变方式.但是没有硬性规则可以告诉您何时发生这种情况.有时候它确实可以实现,什么也不假设.正确的同步绝不应该被跳过.只有使用myBool或ARE/MRE或Interlocked.CompareExchange()的附加锁,您才真正安全.而且,如果您想削减这样一个不稳定的角落,则必须进行检查.

Code is pretty fake of course, nobody will ever write this. In practice this tends to work by accident, you'll have more code inside the while() loop. Too much to allow the jitter to optimize the variable way entirely. But there are no hard rules that will tell you when this happens. Sometimes it does pull it off, assume nothing. Proper synchronization should never be skipped. You really are only safe with an extra lock for myBool or an ARE/MRE or Interlocked.CompareExchange(). And if you want to cut such a volatile corner then you must check.

并在注释中指出,请尝试使用Thread.VolatileRead().您需要使用 byte 而不是 bool .它仍然挂起,不是同步原语.

And noted in the comments, try Thread.VolatileRead() instead. You need to use a byte instead of a bool. It still hangs, it is not a synchronization primitive.

这篇关于在锁语句内部是否仍需要volatile?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆