gpt4 book ai didi

c# - lock 语句中还需要 volatile 吗?

转载 作者:行者123 更新时间:2023-11-30 13:26:14 24 4
gpt4 key购买 nike

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

经过大量研究,我发现 lock 语句至少也会插入 MemoryBarriers

例如:

    public bool stopFlag;

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

但如果我没有完全错的话,JIT 编译器可以自由地从不实际读取循环内的变量,而是它可能只从寄存器中读取变量的缓存版本如果 JIT 对变量进行寄存器分配,AFAIK MemoryBarriers 将无济于事,它只是确保如果我们从内存中读取值是当前值

除非有一些编译器魔术说“如果代码块包含 MemoryBarrier,则在 MemoryBarrier 被阻止后注册所有变量的分配”。

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

最佳答案

每当我看到这样的问题时,我下意识的 react 就是“什么都不假设!” .NET 内存模型非常薄弱,而 C# 内存模型尤其值得注意,因为它使用的语言只能适用于内存模型较弱且不再受支持的处理器。那里的任何东西都不能告诉您这段代码中将要发生什么,您可以对锁和内存屏障进行推理,直到您脸色发青,但您却一无所获。

x64 抖动非常干净,很少会出现意外。但它的日子已经屈指可数了,它将在 VS2015 中被 Ryujit 取代。以 x86 抖动代码库为起点的重写。令人担忧的是,x86 抖动会让您陷入困境。双关语。

最好的办法就是尝试一下,看看会发生什么。稍微重写您的代码并使该循环尽可能紧凑,以便抖动优化器可以做任何它想做的事情:

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());
}

切换到发布版本。 Project + Properties,Build 选项卡,勾选“Prefer 32-bit”选项。 Tools + Options,Debugging,General,取消勾选“Suppress JIT optimization”选项。首先运行调试构建。工作正常,程序在一秒钟后终止。现在切换到 Release 构建,运行并观察它死锁,循环永远不会完成。使用 Debug + Break All 可以看到它在循环中挂了。

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

                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 变量是如何存储在 eax 寄存器中的,cnt 是如何存储在 edx 寄存器中的。抖动优化器的标准职责是使用处理器寄存器并避免内存加载和存储,从而使代码更快。请注意,当它测试该值时,它仍然使用寄存器并且不会从内存中重新加载。因此,这个循环永远不会结束,它总是会挂起你的程序。

当然,代码很假,没有人会写这个。实际上,这往往是偶然工作的,您将在 while() 循环中包含更多代码。太多让抖动完全优化可变方式。但是没有硬性规定会告诉您何时发生这种情况。有时它确实成功了,什么都不假设。永远不应跳过适当的同步。只有为 myBool 或 ARE/MRE 或 Interlocked.CompareExchange() 使用额外的锁,您才真正安全。如果你想削减这样一个不稳定的角落,那么你必须检查。

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

关于c# - lock 语句中还需要 volatile 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29544069/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com