"switch"语句评估线程安全吗?

Mar*_*ari 25 c# thread-safety switch-statement atomicity

请考虑以下示例代码:

class MyClass
{
    public long x;

    public void DoWork()
    {
        switch (x)
        {
            case 0xFF00000000L:
                // do whatever...
                break;

            case 0xFFL:
                // do whatever...
                break;

            default:
                //notify that something going wrong
                throw new Exception();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

忘掉片段的无用性:我的疑问是关于switch声明的行为.

假设该x字段只能有两个值:0xFF00000000L0xFFL.上面的开关不应该属于"默认"选项.

现在想象一个线程正在执行"x"等于0xFFL的开关,因此第一个条件将不匹配.同时,另一个线程将"x"变量修改为0xFF00000000L.我们知道64位操作不是原子操作,因此变量首先将较低的dword置零,然后将较低的dword置零(反之亦然).

如果在"x"为零时(即在新的分配期间)完成开关中的第二个条件,我们是否会陷入不希望的"默认"情况?

Luk*_*keH 16

是的switch,如您的问题所示,声明本身是线程安全的.字段的值一次x加载到(隐藏的)局部变量中,并且该局部用于块.switch

不安全的是将字段初始加载x到局部变量中.64位读取不保证是原子的,因此您可能会在此时获得陈旧和/或撕裂的读取.通过使用Interlocked.Read或类似地以线程安全的方式将字段值显式读入本地,可以很容易地解决这个问题:

long y = Interlocked.Read(ref x);
switch (y)
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)


Jor*_*oba 12

你实际上发布了两个问题.

它是线程安全的吗?

好吧,显然不是,另一个线程可能会在第一个线程进入交换机时更改X的值.由于没有锁定且变量不易变,因此您将根据错误的值进行切换.

你有没有达到开关的默认状态?

理论上你可能,因为更新64位不是原子操作,因此理论上你可以跳转到赋值的中间并得到x的混合结果,如你所指出的那样.这在统计上不会经常发生,但最终会发生.

但是交换机本身是线程安全的,不是线程安全的是读取和写入64位变量(在32位操作系统中).

想象一下,而不是开关(x),你有以下代码:

long myLocal = x;
switch(myLocal)
{
}
Run Code Online (Sandbox Code Playgroud)

现在,切换是通过局部变量进行的,因此,它完全是线程安全的.当然,问题在于myLocal = x 阅读及其与其他任务的冲突.

  • 你的"...想象而不是......"代码几乎就是编译器为`switch`块生成的代码.switch-expression - 从字段`x`加载,在这种情况下 - 只执行一次,结果存储在本地; 然后将本地用于交换机块. (3认同)
  • @Mario Vernari:你已经证明了编译器_can_生成这样的代码,但并不总是这样做.真实编译器受到诸如switch语句数量,其值,所使用的变量数量以及由此产生的内存压力等因素的影响.因此,您看到的反汇编不能代表所有情况. (2认同)
  • @MSalters:switch-expression仅根据[ECMA C#规范](http://www.ecma-international.org/publications/standards/Ecma-334.htm)评估一次(如果你是'15.7.2节)感兴趣). (2认同)