.net 如果我不';不要在多线程中使用锁

.net 如果我不';不要在多线程中使用锁,.net,vb.net,multithreading,.net,Vb.net,Multithreading,如果我的用例要求我可以获得对象的旧状态或新状态,但不是损坏/不一致的状态,那么我应该仍然锁定对象吗 e、 g 在上面的代码中,有两个候选锁-StopFlag和MyQueue 假设用户在大约0.5到1秒的时间内连续排队。间隔当我试图从队列中退出时,是否有人加入队列对我来说真的无关紧要(100毫秒的延迟是可以接受的)。这是因为该项无论如何都将在下一个循环周期中得到处理 出于同样的原因,stopfag状态并不重要。它可能只处理一个或两个额外的循环,这是可以接受的。此类是服务的一部分,除了维护之外,很少

如果我的用例要求我可以获得对象的旧状态或新状态,但不是损坏/不一致的状态,那么我应该仍然锁定对象吗

e、 g

在上面的代码中,有两个候选锁-
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