.net 如果我不';不要在多线程中使用锁
如果我的用例要求我可以获得对象的旧状态或新状态,但不是损坏/不一致的状态,那么我应该仍然锁定对象吗 e、 g 在上面的代码中,有两个候选锁-.net 如果我不';不要在多线程中使用锁,.net,vb.net,multithreading,.net,Vb.net,Multithreading,如果我的用例要求我可以获得对象的旧状态或新状态,但不是损坏/不一致的状态,那么我应该仍然锁定对象吗 e、 g 在上面的代码中,有两个候选锁-StopFlag和MyQueue 假设用户在大约0.5到1秒的时间内连续排队。间隔当我试图从队列中退出时,是否有人加入队列对我来说真的无关紧要(100毫秒的延迟是可以接受的)。这是因为该项无论如何都将在下一个循环周期中得到处理 出于同样的原因,stopfag状态并不重要。它可能只处理一个或两个额外的循环,这是可以接受的。此类是服务的一部分,除了维护之外,很少
StopFlag
和MyQueue
假设用户在大约0.5到1秒的时间内连续排队。间隔当我试图从队列中退出时,是否有人加入队列对我来说真的无关紧要(100毫秒的延迟是可以接受的)。这是因为该项无论如何都将在下一个循环周期中得到处理
出于同样的原因,stopfag
状态并不重要。它可能只处理一个或两个额外的循环,这是可以接受的。此类是服务的一部分,除了维护之外,很少停止服务。因此,将锁用于捕捉偶尔发生的事件似乎有点过头了
我还需要锁定这两个对象吗?在这种情况下,避免使用锁会导致问题吗
换言之,我的
StopFlag
除了真或假之外还能有别的吗?MyQueue.Dequeue
是否会导致对象不是Something
类的有效实例。下面是一些代码,显示了发生了什么。我操纵计时器来显示它是如何失败的。你需要一个有两个按钮的表单
Public Class Form1
Dim testClass1 As New Class1
Dim eq As Threading.Thread
Dim dq As Threading.Thread
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
eq = New Threading.Thread(AddressOf eqThrd)
eq.IsBackground = True
eq.Start()
dq = New Threading.Thread(AddressOf dqThrd)
dq.IsBackground = True
dq.Start()
End Sub
Private Sub eqThrd()
Do While Not testClass1.StopFlag
Try
testClass1.AddItem(New Something)
Catch ex As Exception
'Source array was not long enough. Check srcIndex and length, and the array's lower bounds.
'or
'System.OutOfMemoryException' occurred in System.dll
Debug.WriteLine(ex.Message)
Stop
End Try
Threading.Thread.Sleep(0)
Loop
End Sub
Private Sub dqThrd()
testClass1.Start()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
testClass1.Stop()
End Sub
End Class
Public Class Class1
Public StopFlag As Boolean = False
Private MyQueue As New Queue(Of Something)
Public Sub AddItem(item As Something)
MyQueue.Enqueue(item)
End Sub
Public Sub Start()
DoWork()
End Sub
Public Sub [Stop]()
StopFlag = True
End Sub
Private Sub DoWork()
While Not StopFlag
If MyQueue.Count > 0 Then
Dim item As Something = MyQueue.Dequeue
'do something with this item here..
'I have just put sleep to simulate time spent...
Threading.Thread.Sleep(0)
End If
Threading.Thread.Sleep(100)
End While
End Sub
End Class
Public Class Something
'stub
End Class
我的StopFlag
除了True
或False
之外,还能有别的吗
不,它只有True
或False
,因为分配给布尔
变量是一个原子操作
MyQueue.Dequeue是否会导致对象不是某个类的有效实例
不,这也不会发生。原因是队列
从不操纵某物
的内部状态。它只传递一个指向有效某物实例的指针
如果我的用例要求我可以获得对象的旧状态或新状态,但不是损坏/不一致的状态,那么我应该仍然锁定对象吗
对!!你绝对应该!这同时适用于stopfag
boolean标志和MyQueue
实例
停止标志
出于同样的原因,stopfag
状态并不重要。它可能只处理一个或两个额外的循环,这是可以接受的
问题是,如果没有任何类型的内存同步(锁定为您提供了这种同步),就不能保证循环中的线程将永远看到stopfag
的最新值。因此,尽管在实践中不太可能,但在没有锁定(或其他形式的内存同步)的情况下,stopfag
在循环条件下将永远评估为True
有关此问题的更详细解释,请查看以下线程:
。。。如果你花点时间去寻找它们,还有很多
队列
如果没有任何锁定,肯定有很多方法可以破坏队列的内部状态
,或者让它向您抛出异常
下面是一个非常简单的例子,说明了这是多么容易发生
以下是Queue
的Enqueue
方法的源代码(如果需要,可以查看)。这是C#,但我相信要理解发生了什么不会太难
公共作废排队(T项){
if(\u size==\u array.Length){
int newcapacity=(int)((长)_array.Length*(长)_growthfactor/100);
if(新容量<_array.Length+_MinimumGrow){
新容量=_array.Length+_MinimumGrow;
}
设定容量(新容量);
}
_数组[_tail]=项;
_tail=(_tail+1)%\u array.Length;
_大小++;
_版本++;
}
使用上面的代码作为参考,如果两个线程几乎同时执行下面的代码行(这完全可能没有任何锁定),您认为会发生什么。这样的话,按照这个顺序:
线程1:\u数组[\u尾]=项代码>
线程2:\u数组[\u尾]=项代码>
线程1:\u tail=(\u tail+1)%\u array.Length代码>
线程2:\u tail=(\u tail+1)%\u array.Length代码>
如果您花时间考虑一下,您将看到线程2
会意外地覆盖线程1
排队的项目。因此,该项目现在丢失
此外,\u tail
指针移动两个位置,即使只使用了一个数组插槽。这也带来了其他类型的腐败
这是一个非常简单的例子,还有更多的例子。这甚至不用说,当你在多线程处理时,没有锁定,CPU是如何以一种有趣的、意想不到的方式对指令重新排序的,这会产生其他很难预料的问题
结论
如果需要的话,不要为了获得几毫秒而牺牲正确性。这不值得
多线程是一个非常复杂的主题,很难正确地开始。试图通过采取性能捷径在这一点上变得更聪明是一种灾难。再次强调:不值得。不幸的是,被.NET 3.5卡住了:(是的,我知道在队列(Of T)
上读/写不是线程安全的-这就是为什么这个问题。所以这个程序会失败(运行时错误、损坏的对象状态等)
Public Class Form1
Dim testClass1 As New Class1
Dim eq As Threading.Thread
Dim dq As Threading.Thread
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
eq = New Threading.Thread(AddressOf eqThrd)
eq.IsBackground = True
eq.Start()
dq = New Threading.Thread(AddressOf dqThrd)
dq.IsBackground = True
dq.Start()
End Sub
Private Sub eqThrd()
Do While Not testClass1.StopFlag
Try
testClass1.AddItem(New Something)
Catch ex As Exception
'Source array was not long enough. Check srcIndex and length, and the array's lower bounds.
'or
'System.OutOfMemoryException' occurred in System.dll
Debug.WriteLine(ex.Message)
Stop
End Try
Threading.Thread.Sleep(0)
Loop
End Sub
Private Sub dqThrd()
testClass1.Start()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
testClass1.Stop()
End Sub
End Class
Public Class Class1
Public StopFlag As Boolean = False
Private MyQueue As New Queue(Of Something)
Public Sub AddItem(item As Something)
MyQueue.Enqueue(item)
End Sub
Public Sub Start()
DoWork()
End Sub
Public Sub [Stop]()
StopFlag = True
End Sub
Private Sub DoWork()
While Not StopFlag
If MyQueue.Count > 0 Then
Dim item As Something = MyQueue.Dequeue
'do something with this item here..
'I have just put sleep to simulate time spent...
Threading.Thread.Sleep(0)
End If
Threading.Thread.Sleep(100)
End While
End Sub
End Class
Public Class Something
'stub
End Class