锁定的重要性 - 程度和失败模式

时间:2012-01-09 19:15:09

标签: .net vb.net locking thread-safety

我已经对下面的代码进行了压力测试,似乎工作正常 - 在单个线程编写和单独的线程读取简单变量的简单情况下,不锁定的危险是什么?

有一个有公共财产的班级......

Public Class PropHolder
    Private _myVar As Double
    Public Property myVar() As Double
        Get
            Return _myVar
        End Get
        Set(ByVal value As Double)
            _myVar = value
        End Set
    End Property
End Class

...以及另一个使用此类实例的文件

Public Class Form1 

Public _propHolder As New PropHolder
Delegate Sub dlgPostVar()

    Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        While Not BackgroundWorker1.CancellationPending
            _propHolder.myVar = someValue  'worker thread writing var
        End While
    End Sub

    Private Sub BackgroundWorker2_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker2.DoWork
        While Not BackgroundWorker2.CancellationPending
            Me.Invoke(New dlgPostVar(AddressOf postVal)) 'invoke UI thread to write var
        End While
    End Sub

    Private Sub postVal()
        RichTextBox1.AppendText(_propHolder.myVar.ToString & vbCrLf)
    End Sub

End Class

在上面的示例中,myVar会不断更改并以尽可能快的UI的速度写入RichTextBox。并非所有值都被捕获但这并不重要 - 让我们假设RichTextBox只关心在闲暇时对myVar的当前值进行采样。

我读到的所有内容都让我相信这不安全但我还没有让它失败,即使有两个线程读写(单个读者,单个作家)这个变量不断。这里可能出现什么问题,为什么?

编辑:更具体地说,这个问题比实际更具假设性。我知道这种情况需要“按书”锁定,问题是探索在这种情况下不锁定的潜在后果的广度,希望以所有潜在失败模式的完整集合的形式。

因此,一个例子是从写入的变量中读取:

  • 线程1 - >从内存中读取低位字
  • 线程2 - > CPU将低值的新值移动到内存
  • 线程2 - > CPU将新值的高位移动到内存
  • 线程1 - >从内存中读取高位字

线程1现在是值(HighWord [new])(LowWord [old])** corrupt

程序故障仅限于处理此损坏值所造成的损坏。 这种失败模式的其他后果?有没有?

更多例子?书籍/参考文献欢迎。

编辑#2:我的目的是在.NET框架中探索平台固有的不可预测性和错误的程度和形式,特别是与任何特定程序随后可能失败的后果无关。面对这样的错误。

在上面的示例中,除了将其作为文本写出之外,我什么也没做什么。如果我得到一个不好的价值,我会给文本写一个不好的价值 - 没有爆炸,程序不会崩溃,帝国不会下降,而且水星的轨道保持稳定。

3 个答案:

答案 0 :(得分:3)

在这种情况下你需要一个锁,因为对double的变量写入不是原子的。因此,这可能会破裂,极不可能。

答案 1 :(得分:0)

杰森指出,你确实需要锁定。您可能还没有看到这种情况,因为正如其他人所指出的那样,线程问题非常难以重现。它可能需要一组特定的环境(包括用户负载,其他进程,可用内存量,应用程序中运行的并发线程数等)来触发这种不幸的情况。

这个想法应该相当简单明了。如果单独的执行线程将触及变量或其他资源,请在执行此操作之前将其锁定。这可以确保请求线程可以单独访问变量或资源,直到它完成它为止,避免了您损坏数据的可能性,或者进入使您的机器陷入停顿的其他令人讨厌的情况。

有一个很好的理由,MSDN上的每个类都有线程安全文档。微软认真对待它,你也应该这样做。

答案 2 :(得分:-1)

仅仅因为你测试不会导致问题并不意味着它不会发生。线程问题是最难调试的。锁定代码的原因是为了防止输出日期信息或导致双重处理。可能发生的问题是一个线程设置值,然后另一个线程覆盖它而不允许它处理它。在你的情况下,你可能会遇到一个问题,它正在写入变量,因为另一个线程正在读取它,因此导致了一个问题。

来自MSDN:

“此类型的所有成员都是线程安全的。似乎修改实例状态的成员实际上返回一个使用新值初始化的新实例。与任何其他类型一样,读取和写入包含此实例的共享变量必须通过锁保护类型以保证线程安全。

注意

在所有硬件平台上分配此类型的实例并非线程安全,因为该实例的二进制表示可能太大而无法在单个原子操作中进行分配。