8

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

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

 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();

然后我在并行循环中写了我的第一次尝试:

 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();

当我在设置为仅使用 1 个核心的虚拟机上运行它时,我观察到类似的持续时间,但运行不再按顺序处理 - 不足为奇。

当我在双核机器上运行它时,事情变得很奇怪。我在时间上没有看到任何改进,并且每次运行都观察到一些非常奇怪的结果。大多数运行的结果是 -1,000,000(或非常接近),这表明 Random.Next 始终返回 0 准。

当我对每个循环进行随机本地化时,一切正常,并且我得到了预期的持续时间改进:

Parallel.For(0, 20, run =>
         {
            var localRandom = new Random();
            var position = 0; 

我的猜测是,问题与 Random 对象在循环之间共享并具有某种状态的事实有关。我认为“并行失败”版本的持续时间缺乏改进是因为对 Random 的调用没有并行处理(即使我看到并行版本使用两个内核,而原始版本没有) . 我真的不明白为什么模拟结果是这样的。

我有一个单独的担忧是,如果我在每个循环本地使用 Random 实例,我可能会遇到多个循环从同一个种子开始的问题(当你生成多个 Random 的时间太接近时会出现问题,导致相同的序列)。

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

4

3 回答 3

2

该类Random不是线程安全的;如果你在多个线程上使用它,它可能会搞砸。

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

于 2010-05-27T20:26:24.480 回答
2

这些方法都不会为您提供非常好的随机数。

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

http://blogs.msdn.com/b/pfxteam/archive/2009/02/19/9434171.aspx

这些对于许多日常应用程序来说可能都很好。

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

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

http://software.intel.com/en-us/videos/tim-mattson-use-and-abuse-of-random-numbers/

如果你想要真正的随机数,那么你真的需要使用加密随机数生成器 System.Security.Cryptography.RNGCryptoServiceProvider。这是线程安全的。

于 2010-06-25T03:15:24.283 回答
1

一个核心问题:

  • random.Next不是线程安全的。

两个后果:

  1. 随机性的质量被竞争条件破坏。
  2. 虚假共享会破坏多核的可扩展性。

几种可能的解决方案:

  • 使random.Next线程安全:解决质量问题但不解决可伸缩性问题。
  • 使用多个 PRNG:解决可扩展性问题,但可能会降低质量。
  • ...
于 2012-12-28T21:31:14.210 回答