“switch”语句评估是否是线程安全的?

请考虑以下示例代码:

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

忘掉片段的无用性:我怀疑是关于switch语句的行为。

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

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

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

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

它是线程安全的吗?

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

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

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

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

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

 long myLocal = x; switch(myLocal) { } 

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

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

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

 long y = Interlocked.Read(ref x); switch (y) { // ... } 

C#的switch语句不被评估为一系列if条件(如VB所示)。 C#有效地构建了一个标签的哈希表,可以根据对象的值跳转到并直接跳转到正确的标签,而不是依次迭代每个条件并对其进行评估。

这就是为什么C#switch语句在增加案例数量时不会在速度方面恶化。 这也是为什么C#在开关情况下比VB更具限制性的原因,例如,你可以在VB中执行各种值。

因此,您没有已经说明的潜在竞争条件,进行比较,更改值,进行第二次比较等,因为只执行了一次检查。 至于它是否完全是线程安全的 – 我不会这么认为。

通过IL中的C#switch语句查看reflection器,您将看到发生了什么。 将它与VB中的switch语句进行比较,其中包含值中的范围,您将看到差异。

我看了它已经有好几年了,所以事情可能会略有改变……

在此处查看有关switch语句行为的更多详细信息: 在C#中使用if / else和switch-case之间是否存在任何显着差异?

正如您已经假设的那样,switch语句不是线程安全的,并且在某些情况下可能会失败。

此外,在实例变量上使用lock也不会起作用,因为lock语句需要一个object导致您的实例变量被装箱。 每次加载实例变量时,都会创建一个新的盒装变量,使lock无效。

在我看来,你有几个选择来解决这个问题。

  1. 对任何引用类型的私有实例变量使用lockobject将完成工作)
  2. 使用ReaderWriterLockSlim让多个线程读取实例变量,但一次只有一个线程写入实例变量。
  3. 以primefaces方式将实例变量的值存储到方法中的局部变量(例如,使用Interlocked.ReadInterlocked.Exchange )并对局部变量执行switch 。 请注意,这样您可以使用旧值作为switch 。 您必须确定这是否会导致您的具体用例出现问题。
Interesting Posts