Random 并行循环和随机循环产生奇数结果

Random 并行循环和随机循环产生奇数结果,random,parallel-processing,simulation,task-parallel-library,Random,Parallel Processing,Simulation,Task Parallel Library,我刚开始玩任务并行库,遇到了一些有趣的问题;我对正在发生的事情有一个大致的想法,但我希望听到比我更有能力的人的评论,以帮助理解正在发生的事情。很抱歉代码有点长 我从随机行走的非平行模拟开始: var random = new Random(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); var simulations = new List<int>(); for (var run = 0; run

我刚开始玩任务并行库,遇到了一些有趣的问题;我对正在发生的事情有一个大致的想法,但我希望听到比我更有能力的人的评论,以帮助理解正在发生的事情。很抱歉代码有点长

我从随机行走的非平行模拟开始:

 var random = new Random();
 Stopwatch stopwatch = new Stopwatch();

 stopwatch.Start();

 var simulations = new List<int>();
 for (var run = 0; run < 20; run++)
 {
    var position = 0;
    for (var step = 0; step < 10000000; step++)
    {
       if (random.Next(0, 2) == 0)
       {
          position--;
       }
       else
       {
          position++;
       }
    }

    Console.WriteLine(string.Format("Terminated run {0} at position {1}.", run, position));
    simulations.Add(position);
 }

 Console.WriteLine(string.Format("Average position: {0} .", simulations.Average()));
 stopwatch.Stop();

 Console.WriteLine(string.Format("Time elapsed: {0}", stopwatch.ElapsedMilliseconds));
 Console.ReadLine();
我的猜测是,问题与以下事实有关:随机对象在循环之间共享,并且具有某种状态。“失败的并行”版本在持续时间方面缺乏改进,我认为这是因为对Random的调用不是并行处理的(尽管我看到并行版本使用两个内核,而原始版本没有)。我真的不明白为什么模拟结果是这样的

我的另一个担忧是,如果我使用每个循环的本地随机实例,我可能会遇到多个循环从同一个种子开始的问题(当您在时间上生成多个过于接近的随机数,导致相同的序列时,会遇到这个问题)


任何对正在发生的事情的洞察对我来说都是非常有价值的

随机类不是线程安全的;如果在多个线程上使用它,它可能会出错


您应该在每个线程上创建一个单独的
Random
实例,并确保它们不会使用相同的种子。(例如,
Environment.TickCount*Thread.CurrentThread.ManagedThreadId

这两种方法都不能提供真正好的随机数

这篇博文涵盖了许多用随机数获得更好随机数的方法

对于许多日常应用来说,这些可能是好的

但是,如果在多个线程上使用相同的随机数生成器,即使使用不同的种子,也会影响随机数的质量。这是因为您正在生成可能重叠的伪随机数序列

本视频更详细地解释了原因:

如果您想要真正的随机数,那么您确实需要使用crypto random number generator System.Security.Cryptography.RNGCryptoServiceProvider。这是线程安全的。

一个核心问题:

  • random.Next
    不是线程安全的
两个后果:

  • 随机性的质量被种族条件破坏
  • 错误共享会破坏多核上的可伸缩性
  • 几种可能的解决办法:

    • 使
      随机。下一步
      线程安全:解决质量问题,但不解决可伸缩性
    • 使用多个PRNG:解决了可伸缩性问题,但可能会降低质量

    您将如何处理种子问题?事件如果您采用这种方法(存在问题),我不会使用TickCount*ManageThreadId,因为这将产生非常紧密的种子。请参阅下面我的答案,以获得生成种子的更好方法。Ade,感谢您提供指向S.Toub文章的指针,它非常棒。
     var localRandom = new Random();
    
     stopwatch.Reset();
     stopwatch.Start();
    
     var parallelSimulations = new List<int>();
     Parallel.For(0, 20, run =>
     {
        var position = 0;
        for (var step = 0; step < 10000000; step++)
        {
           if (localRandom.Next(0, 2) == 0)
           {
              position--;
           }
           else
           {
              position++;
           }
        }
    
        Console.WriteLine(string.Format("Terminated run {0} at position {1}.", run, position));
        parallelSimulations.Add(position);
     });
    
    
     Console.WriteLine(string.Format("Average position: {0} .", parallelSimulations.Average()));
     stopwatch.Stop();
    
     Console.WriteLine(string.Format("Time elapsed: {0}", stopwatch.ElapsedMilliseconds));
    
     Console.ReadLine();
    
    Parallel.For(0, 20, run =>
             {
                var localRandom = new Random();
                var position = 0;