随机线程安全的以下VB.NET实现?

时间:2017-03-07 01:30:35

标签: c# vb.net multithreading random

我知道有许多线程安全问题,特别是关于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真的是线程安全的吗?如果没有,是否可以在不完全改变实现的情况下以线程安全的方式实现它?

1 个答案:

答案 0 :(得分:4)

您询问您的实现是否是线程安全的。我会说,在某些这个词的意义上,是的。但在这里小心。 What is this thing you call "thread safe"?。我同意代码是线程安全的,因为程序将安全运行而不会破坏数据结构。但是,在并发场景下,代码是否可以满足您的需求(包括满足您可能没有意识到的目标)?这是值得商榷的。

我认为我发布的代码首先要担心的是它是否真的有用。我突出了三个主要问题:

  1. 想要使用单个种子值并在给定范围内获取一系列随机数的调用者运气不佳。每次调用Random时,他们都会得到一个全新的RandomInteger()实例,该实例将始终返回相同的数字。
  2. 您只使用16位Tick值。这似乎是不必要的熵减少。为什么不使用&H7FFFFFFF
  3. 实际上没有任何东西阻止两个不同的线程获得相同的种子(似乎是你的目标)。
  4. 创建Random的实例并不便宜。因此,这种线程安全方法非常昂贵。我确信与仅使用lock相比,后者将获胜。
  5. 除此之外,当然还有一个问题,即通过使用递增值为它们设置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;
        }
    }
    

    如果您需要多个线程以相关的随机数序列成为问题的方式一起操作,那么您应该只使用Randomlock个实例:

    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