我知道有许多线程安全问题,特别是关于VB.NET和C#中的Random类。我已经阅读了Jon Skeet的几篇以及一篇博客文章(https://codeblog.jonskeet.uk/2009/11/04/revisiting-randomness/);但是我仍然很难评估我的代码的线程安全性。我为此添加另一个问题表示道歉。
我的代码基于上述博文,但我尝试实施帖子底部附近的Andrew Shapira的评论。
后台:任何调用此类(直接或间接)的代码通常在多个线程上运行,而不使用任何类型的线程锁定。虽然我知道我可以将线程安全的责任传递给任何调用代码,但我更愿意让这个类自己处理多个线程。
Option Explicit On
Option Strict On
Imports System.Threading
Public NotInheritable Class ThreadSafeRandom
Private Shared globalSeed As Integer = 0
Private Shared Function CreateRandom(ByVal initialSeed As Integer) As Random
Dim newRandom As Random
If initialSeed <> 0 Then
newRandom = New Random(initialSeed)
Return newRandom
End If
Dim seed As Integer = CInt(Now.Ticks And &HFFFF)
Interlocked.CompareExchange(globalSeed, seed, 0)
newRandom = New Random(globalSeed)
Interlocked.Increment(globalSeed)
Return newRandom
End Function
Public Shared Function RandomInteger(ByVal lowerBound As Integer,
ByVal exclusiveUpperBound As Integer,
Optional ByVal seed As Integer = 0) As Integer
Dim newRandom As Random = CreateRandom(seed)
Return newRandom.Next(lowerBound, exclusiveUpperBound)
End Function
End Class
解释:globalSeed
是默认情况下传递给Random
构造函数的值。每次传递时,它都会通过System.Threading.Interlocked
函数中的CreateRandom
递增。
CreateRandom
是在Private
函数Public
中调用的RandomInteger
函数。 CreateRandom
的双重目的是确保永远不会传递相同的种子值,同时允许始终创建新的Random
对象,从而避免潜在的线程问题。
最后,为了允许用户复制结果,该类允许用户输入的种子值。
问题:ThreadSafeRandom真的是线程安全的吗?如果没有,是否可以在不完全改变实现的情况下以线程安全的方式实现它?
答案 0 :(得分:4)
您询问您的实现是否是线程安全的。我会说,在某些这个词的意义上,是的。但在这里小心。 What is this thing you call "thread safe"?。我同意代码是线程安全的,因为程序将安全运行而不会破坏数据结构。但是,在并发场景下,代码是否可以满足您的需求(包括满足您可能没有意识到的目标)?这是值得商榷的。
我认为我发布的代码首先要担心的是它是否真的有用。我突出了三个主要问题:
Random
时,他们都会得到一个全新的RandomInteger()
实例,该实例将始终返回相同的数字。Tick
值。这似乎是不必要的熵减少。为什么不使用&H7FFFFFFF
?Random
的实例并不便宜。因此,这种线程安全方法非常昂贵。我确信与仅使用lock
相比,后者将获胜。除此之外,当然还有一个问题,即通过使用递增值为它们设置Random
个对象来创建它们。请参阅Jon's comment及以下所引用的各种文章。
基于这些参考文献中的有价值的讨论,我会说根据您的需要,有两种明显,简单且有用的方法来解决问题。
如果每个线程完全独立地运行,因此Random
的多个实例之间的任何关联都是无关紧要的,那么ThreadLocal<T>
或[ThreadStatic]
方法应该没问题。 E.g:
private static readonly ThreadLocal<Random> _threadRandom =
new ThreadLocal<Random>(() => new Random());
public static Random Instance { get { return _threadRandom.Value; } }
...或
[ThreadStatic]
private static Random _threadRandom;
public static Random Instance
{
get
{
if (_threadRandom == null)
{
_threadRandom = new Random();
}
return _threadRandom;
}
}
如果您需要多个线程以相关的随机数序列成为问题的方式一起操作,那么您应该只使用Random
个lock
个实例:
static class SharedRandom
{
private static readonly Random _random = new Random();
public static int Next()
{
lock (_random)
{
return _random.Next();
}
}
// etc...wrap each `Random` method you need, as above
}
有用的参考资料(你知道所有这些......为了方便起见,我只是将它们放在一个地方,因为一对夫妇在评论中,这是短暂的):
Seeding Multiple Random Number Generators
C# in Depth: Random numbers
How do I seed a random class to avoid getting duplicate random values
Jon Skeet's coding blog: Revisiting randomness