在.Net中使用Random的并发问题?

时间:2011-05-06 18:13:08

标签: c# .net concurrency random

我用Paint.Net plugin调试了一些问题,当有几个线程从单个实例调用一个方法时,我偶然发现了Random类的一些问题。

由于一些奇怪的原因,似乎如果我不阻止并发访问,通过同步被调用的方法,我的Random实例开始表现......随机(但在不好的意义上)。

在下面的示例中,我创建了几百个线程,它们可以重复地调用一个Random对象。当我运行它时,我有时(不总是,但几乎)得到明显错误的结果。如果我取消注释Synchronized方法注释,那么问题永远不会发生。

using System;
using System.Threading;
using System.Runtime.CompilerServices;
namespace testRandom {

    class RandTest  {
        static int NTIMES = 300;

        private long ac=0;

        public void run() { // ask for random number 'ntimes' and accumulate
            for(int i=0;i<NTIMES;i++) {
                ac+=Program.getRandInt();
                System.Threading.Thread.Sleep(2);
            }
        }

        public double getAv() { 
            return ac/(double)NTIMES; // average
        }
    }

    class Program
    {

        static Random random = new Random();
        static int MAXVAL = 256;
        static int NTREADS = 200;

        //[MethodImpl(MethodImplOptions.Synchronized)]
        public static int getRandInt() { 
            return random.Next(MAXVAL+1); // returns a value between 0 and MAXVAL (inclusive)
        }

        public static void Main(string[] args)      {
            RandTest[] tests = new RandTest[NTREADS];
            Thread[] threads = new Thread[NTREADS];
            for(int i=0;i<NTREADS;i++)  {
                tests[i]= new RandTest();
                threads[i] = new Thread(new ThreadStart(tests[i].run));
            }
            for(int i=0;i<NTREADS;i++)  threads[i].Start();
            threads[0].Join();
            bool alive=true;
            while(alive) { // make sure threads are finished
                alive = false;
                for(int i=0;i<NTREADS;i++)  { if(threads[i].IsAlive) alive=true; }
            }
            double av=0;
            for(int i=0;i<NTREADS;i++)  av += tests[i].getAv();
            av /= NTREADS;
            Console.WriteLine("Average:{0, 6:f2}   Expected:{1, 6:f2}",av,MAXVAL/2.0);

            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
    }
}

示例输出(具有上述值):

Average: 78.98   Expected:128.00
Press any key to continue . . .

这是一个已知的问题吗?从没有同步的多个线程调用Random对象是不正确的吗?

更新:根据答案,文档声明随机方法不是线程安全的 - mea culpa,我应该已经读过。也许我之前已经阅读过但不认为它如此重要 - 人们可以(懒散地)认为,在两个线程同时进入同一方法的极少数情况下,可能发生的最坏情况是那些调用得到错误的结果 - 不是如果我们不太关心随机数质量......那么这个问题确实是灾难性的,因为对象处于不一致的状态,并且从那里开始返回零 - 如前所述{{3} }。

4 个答案:

答案 0 :(得分:17)

  

出于某种奇怪的原因

这并不奇怪 - Random documented不是线程安全的。

这是一种痛苦,但这就是生活。有关更多信息,请参阅我的article on Random,以及有关如何为每个线程创建实例的建议,以防止在多个线程中使用相同的种子启动。

答案 1 :(得分:8)

Random类不是线程安全的。

来自docs

Any instance members are not guaranteed to be thread safe

尝试实现ThreadStatic属性,而不是同步哪个会导致所有线程阻塞。

答案 2 :(得分:1)

Random不保证是线程安全的:http://msdn.microsoft.com/en-us/library/system.random.aspx除非它是公共静态的。

答案 3 :(得分:1)

不幸的是,这是正确的,使用随机类时必须小心。

以下是两篇博客文章,其中包含有关此主题的更多详细信息,评论和代码示例:

这种行为最糟糕的部分是它只是停止工作(即一旦出现问题,'random.Next ....'方法的返回值为0)