Option Strict On
Public Class UtilityClass
Private Shared _MyVar As String
Public Shared ReadOnly Property MyVar() As String
Get
If String.IsNullOrEmpty(_MyVar) Then
_MyVar = System.Guid.NewGuid.ToString()
End If
Return _MyVar
End Get
End Property
Public Shared Sub SaveValue(ByVal newValue As String)
_MyVar = newValue
End Sub
End Class
答案 0 :(得分:5)
虽然锁定是添加线程安全性的一种很好的通用方法,但在涉及一次写入准不可变性的许多场景中,只要向其写入非空值,字段就应该变为不可变,Threading.Interlocked.CompareExchange
可能更好。本质上,该方法读取一个字段,并且 - 在其他人可以触摸它之前 - 当且仅当该字段与提供的“compare”值匹配时才写入新值;它返回在任何情况下读取的值。如果两个线程同时尝试CompareExchange
,并且两个线程都将字段的当前值指定为“比较”值,则其中一个操作将更新该值,而另一个操作将不会,并且每个操作将“知道”是否为成功了。
CompareExchange有两种主要的使用模式。第一个对于生成可变单例对象最有用,其中每个人都看到相同的实例很重要。
If _thing is Nothing then
Dim NewThing as New Thingie() ' Or construct it somehow
Threading.Interlocked.CompareExchange(_thing, NewThing, Nothing)
End If
这种模式可能就是你所追求的。请注意,如果一个线程在另一个线程完成之间以及它执行CompareExchange
的时间之间输入上述代码,则两个线程可能最终创建一个新的Thingie
。如果发生这种情况,首先到达CompareExchange的线程将使其新实例存储在_thing中,而另一个线程将放弃其实例。在这种情况下,线程不关心它们是赢还是输; _thing将在其中包含一个新实例,并且所有线程都将在那里看到相同的实例。还要注意,因为在第一次读取之前没有内存屏障,理论上有可能在过去的某个时间检查过_thing的值的线程可能会继续将其视为Nothing
,直到某些内容导致它更新其缓存,但如果发生这种情况,唯一的后果就是创建一个无用的Thingie
新实例,当Interlocked.CompareExchange
发现_thing
已被写入时,该实例将被丢弃。
另一个主要用法模式对于更新对不可变对象的引用很有用,或者 - 稍作调整 - 更新某些值类型,如Integer或Long。
Dim NewThing, WasThing As Thingie
Do
WasThing = _thing
NewThing = WasThing.WithSomeChange();
Loop While Threading.Interlocked.CompareExchange(_thing, NewThing, WasThing) IsNot WasThing
在这种情况下,假设有一些方法,给定对Thingie的引用,可以廉价地生成一个以某种所需方式不同的新实例,可以以线程安全的方式对_thing执行任何此类操作。例如,给定String
,可以轻松生成一个附加了一些字符的新String
。如果有人希望以线程安全的方式将某些文本附加到字符串(如果一个线程尝试添加Fred
而另一个尝试添加Joe
,则最终结果将是追加FredJoe
或JoeFred
,而不是FrJoeed
),上面的代码会让每个帖子都读_thing
,生成一个附加了文字的版本,然后尝试更新{ {1}}。如果某个其他线程在平均时间内更新_thing
,则放弃构造的最后一个字符串,根据更新的_thing
创建一个新字符串,然后重试。
请注意,虽然这种方法不一定比锁定方法更快,但它确实提供了一个优势:如果获取锁的线程陷入无限循环或者其他方式,则所有线程将永远被阻止访问锁定资源。相比之下,如果上面的_thing
方法陷入无限循环,WithSomeChanges()
的其他用户将不会受到影响。
答案 1 :(得分:3)
对于多线程代码,相关问题是:可以从多个线程修改状态吗?如果是这样,代码就不是线程安全的。
在你的代码中,情况就是这样:有几个地方变异_MyVar
,因此代码不是线程安全的。使代码线程安全的最佳方法是几乎总是使其不可变:默认情况下,immutable状态只是线程安全。此外,不修改线程状态的代码比更改多线程代码更简单,通常更有效。
不幸的是,没有上下文就不可能看到你的代码是否(或如何)可以从几个线程变成不可变的。因此,我们需要采用缓慢,容易出错的锁定(请参阅另一个答案,了解错误是多么容易)并给出错误的安全感。
以下是我尝试使用锁来使代码更正。 应该工作(但要记住错误的安全感):
Public Class UtilityClass
Private Shared _MyVar As String
Private Shared ReadOnly _LockObj As New Object()
Public Shared ReadOnly Property MyVar() As String
Get
SyncLock _LockObj
If String.IsNullOrEmpty(_MyVar) Then
_MyVar = System.Guid.NewGuid.ToString()
End If
Return _MyVar
End SyncLock
End Get
End Property
Public Shared Sub SaveValue(ByVal newValue As String)
SyncLock _lockObj
_MyVar = newValue
End SyncLock
End Sub
End Class
一些评论:
_MyVar
,因为我们更改 _MyVar
的引用,从而失去了我们的锁定。我们需要一个单独的专用锁定对象。总而言之,更改代码设计要容易得多,因此一次只能有一个线程对任何给定变量具有写访问权限,并且通过{{将线程之间的所有必要通信限制为明确定义的通信通道3}}