在foreach循环中,C++运算符是否变得线程安全



最近我从VB转到了C#,所以我经常使用C#到VB.NET的转换器来理解语法差异。在将下一个方法移到VB时,我注意到了一件有趣的事情。

C#原始代码:

 public bool ExceedsThreshold(int threshold, IEnumerable<bool> bools)
{
   int trueCnt = 0;
   foreach(bool b in bools)
      if (b && (++trueCnt > threshold)) 
          return true;
   return false;          
} 

VB.NET结果:

Public Function ExceedsThreshold(threshold As Integer, bools As IEnumerable(Of Boolean)) As Boolean
Dim trueCnt As Integer = 0
For Each b As Boolean In bools
    If b AndAlso (System.Threading.Interlocked.Increment(trueCnt) > threshold) Then
        Return True
    End If
Next
Return False End Function

System.Threading.Interlocked.Increment替换C#的++运算符如果在foreach循环中使用,这是否意味着非线程安全的++运算符将变为线程安全的?这是一种语法糖吗?若那个是真的,那个么转换器为什么把Interlocked.Increment放在VB版本中呢?我认为foreach在C#和VB中的工作原理完全相同。或者它只是转换器的"保险"?

我确信这只是一个转换器破解,我想我可以解释这背后的原因。

但首先,为了回答您的问题,C#中内置的++运算符不是线程安全的。它只是以下过程的语法糖(在++i的情况下):

  • 读取i的值
  • 递增
  • 将其写回i
  • 返回递增的值

由于存在单独的读写操作,因此这是一个非原子操作。

现在,在VB中没有++运算符的直接等价物。最接近的是:

i += 1

这是一个语句。相比之下,++i是一个表达式。您可以在另一个语句或表达式中使用++i,但不能在VB语句中使用。

使用Interlocked.Increment只是一种轻松翻译代码的聪明方法,而不必将整个语句分解为多个其他语句。

如果没有这个技巧,转换器将不得不分解如下表达式:

if (b && (++trueCnt > threshold))
    ...
If b Then
    trueCnt += 1
    If trueCnt > threshold Then
        ...
    End If
End If

正如你所看到的,这需要更多的重写。如果trueCnt是一个属性,它甚至需要引入一个单独的临时变量(以避免对其求值两次)。

这需要比转换器使用的更简单的语法转换更深入的语义分析和控制流重写,因为trueCnt += 1不能在VB.

中的表达式内部使用

我相信这是因为您希望在进行比较的同时增加同一语句中的值。我对此没有太多的理由,但这确实是正确的答案,因为trueCnt+=1不允许在同一行中进行比较。这当然与它是foreach无关,试着在循环外添加同一行,我几乎可以肯定它也会转换为Increment。VB.Net中根本没有其他语法可以在同一行中同时进行增量和比较。

除了上述问题外,C#的默认行为遵循Java不幸的整数溢出行为。尽管有时包装整数溢出语义很有用,但C#通常不会区分整数应该在溢出时包装的情况与整数不应该包装但程序员认为溢出捕获不值得的情况。这可能会混淆转换工作,因为VB.NET使捕获行为比包装行为更容易、更快,而C#则相反。因此,出于速度原因,翻译使用未检查数学的代码的逻辑方法是在VB.NET中使用普通的检查数学,而翻译需要包装行为的代码的合乎逻辑的方法是在VB.NET中使用包装整数方法。Threading.Interlocked.IncrementThreading.Increment.Add方法使用包装整数行为,因此,虽然从速度的角度来看它们不是最优的,但它们很方便。

最新更新