gpt4 book ai didi

c# - 为什么 Interlocked.Increment 在 Parallel.ForEach 循环中给出不正确的结果?

转载 作者:可可西里 更新时间:2023-11-01 09:08:57 27 4
gpt4 key购买 nike

我有一项迁移工作,完成后我需要验证目标数据。为了通知管理员验证成功/失败,我使用计数器比较数据库 1 中表 Foo 的行数与数据库 2 中表 Foo 的行数。

Database2 中的每一行都根据 Database1 中的相应行进行验证。为了加快这个过程,我使用了一个 Parallel.ForEach 循环。

我最初的问题是计数总是与我的预期不同。后来发现+=-=操作不是线程安全的(不是原子的)。为解决此问题,我更新了代码以在计数器变量上使用 Interlocked.Increment。这段代码打印出一个更接近实际计数的计数,但是,每次执行似乎都不同,它没有给出我期望的结果:

Private countObjects As Integer

Private Sub MyMainFunction()
Dim objects As List(Of MyObject)

'Query with Dapper, unrelevant to the problem.
Using connection As New System.Data.SqlClient.SqlConnection("aConnectionString")
objects = connection.Query("SELECT * FROM Foo") 'Returns around 81000 rows.
End Using

Parallel.ForEach(objects, Sub(u) MyParallelFunction(u))

Console.WriteLine(String.Format("Count : {0}", countObjects)) 'Prints "Count : 80035" or another incorrect count, which seems to differ on each execution of MyMainFunction.
End Sub

Private Sub MyParallelFunction(obj As MyObject)
Interlocked.Increment(countObjects) 'Breakpoint Hit Count is at around 81300 or another incorrect number when done.

'Continues executing unrelated code using obj...
End Sub

在用其他方法使增量线程安全进行一些实验后,我发现将增量包装在虚拟引用对象上的 SyncLock 中会得到预期的结果:

Private countObjects As Integer
Private locker As SomeType

Private Sub MyMainFunction()
locker = New SomeType()
Dim objects As List(Of MyObject)

'Query with Dapper, unrelevant to the problem.
Using connection As New System.Data.SqlClient.SqlConnection("aConnectionString")
objects = connection.Query("SELECT * FROM Foo") 'Returns around 81000 rows.
End Using

Parallel.ForEach(objects, Sub(u) MyParallelFunction(u))

Console.WriteLine(String.Format("Count : {0}", countObjects)) 'Prints "Count : 81000".
End Sub

Private Sub MyParallelFunction(obj As MyObject)
SyncLock locker
countObjects += 1 'Breakpoint Hit Count is 81000 when done.
End SyncLock

'Continues executing unrelated code using obj...
End Sub

为什么第一个代码片段没有按预期工作?最令人困惑的是断点命中计数给出了意想不到的结果。

我对 Interlocked.Increment 或原子操作的理解有缺陷吗?我不希望在虚拟对象上使用 SyncLock,我希望有一种方法可以干净地做到这一点。

更新:

  • 我在 Any CPU 上以 Debug 模式运行示例。
  • 我在堆栈的上层使用 ThreadPool.SetMaxThreads(60, 60),因为我在某个时候查询 Access 数据库。这会导致问题吗?
  • Increment 的调用是否会干扰 Parallel.ForEach 循环,迫使它在所有任务完成之前退出?

更新 2(方法论):

  • 我的测试使用与此处显示的代码尽可能接近的代码执行,但对象类型和查询字符串除外。
  • 查询总是给出相同数量的结果,我总是在断点处验证 objects.Count,然后再继续 Parallel.ForEach
  • 唯一在两次执行之间发生变化的代码是 Interlocked.Increment 替换为 SyncLock lockercountObjects += 1

更新 3

我通过在新的控制台应用程序中复制我的代码并替换外部类和代码来创建 SSCCE。

这是控制台应用程序的 Main 方法:

Sub Main()
Dim oClass1 As New Class1
oClass1.MyMainFunction()
End Sub

这是 Class1 的定义:

Imports System.Threading

Public Class Class1

Public Class Dummy
Public Sub New()
End Sub
End Class

Public Class MyObject
Public Property Id As Integer

Public Sub New(p_Id As Integer)
Id = p_Id
End Sub
End Class

Public Property countObjects As Integer
Private locker As Dummy

Public Sub MyMainFunction()
locker = New Dummy()
Dim objects As New List(Of MyObject)

For i As Integer = 1 To 81000
objects.Add(New MyObject(i))
Next

Parallel.ForEach(objects, Sub(u As MyObject)
MyParallelFunction(u)
End Sub)

Console.WriteLine(String.Format("Count : {0}", countObjects)) 'Interlock prints an incorrect count, different in each execution. SyncLock prints the correct count.
Console.ReadLine()
End Sub

'Interlocked
Private Sub MyParallelFunction(ByVal obj As MyObject)
Interlocked.Increment(countObjects)
End Sub

'SyncLock
'Private Sub MyParallelFunction(ByVal obj As MyObject)
' SyncLock locker
' countObjects += 1
' End SyncLock
'End Sub

End Class

当将 MyParallelFunctionInterlocked.Increment 切换到 SyncLock 时,我仍然注意到相同的行为。

最佳答案

属性上的

Interlocked.Increment 总是会被破坏。实际上,VB 编译器将其重写为:

Value = <value from Property>
Interlocked.Increment(Value)
<Property> = Value

因此破坏了 Increment 提供的任何线程保证。将其更改为字段。 VB 会将作为 ByRef 参数传递的任何属性重写为类似于上述代码的代码。

关于c# - 为什么 Interlocked.Increment 在 Parallel.ForEach 循环中给出不正确的结果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20619553/

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