Parallel.For和For产生不同的结果

如果我运行此测试:

var r = new Random(); var ints = new int[13]; Parallel.For(0, 2000000, i => { var result = r.Next(1, 7) + r.Next(1, 7); ints[result] += 1; }); 

我得到以下结果:

 2: 92,14445 3: 0,41765 4: 0,62245 5: 0,82525 6: 1,04035 7: 1,25215 8: 1,0531 9: 0,8341 10: 0,6334 11: 0,4192 12: 0,2109 

当我使用常规For:

 for (int i = 0; i < 2000000; i++) { var result = r.Next(1, 7) + r.Next(1, 7); ints[result] += 1; } 

输出是:

 2: 2,7797 3: 5,58645 4: 8,3414 5: 11,09935 6: 13,8909 7: 16,6731 8: 13,82895 9: 11,10205 10: 8,3424 11: 5,5712 12: 2,7845 

最后一个结果是三角分布,它是预期的输出。

我的问题的目的不是讨论并行性的适用性。 问题是为什么Parallel.For表现那样?

Random类方法不是线程安全的。

http://msdn.microsoft.com/en-us/library/system.random.next(v=vs.90).aspx#2

所以第一段代码只是展示了一些未定义的行为。

编辑:

至于一点推测,从我对操作系统的了解很少,我相信随机数生成是一个相当低级别的操作,因此甚至可能需要上下文切换。 在发生这种情况时,您可能会在有机会更新之前多次抓取相同的随机数。 这将导致不平衡的分布。

除了@ spencerruport断言Random类不是线程安全的,你的并行代码也不是线程安全的:

  Parallel.For(0, 2000000, i => { //say two threads produce same total at same time var result = r.Next(1, 7) + r.Next(1, 7); //what happens on the next line when a context-switch //occurs during this non-atomic operation? ints[result] += 1; }); 

利用PLINQ代表您收集结果可能更好:

 Enumerable.Range(0, 2000000) .AsParallel() .Select(_ => SafeRandom(1, 7) + SafeRandom(1, 7)) .GroupBy(x => x) .Select(g => new {value = g.Key, frequency = g.Count()}) 

而不是自己管理对共享内存(上面的ints数组)的访问。

SafeRandom的合理实现可能如下所示:

 private static int seedUnique=0; private static ThreadLocal tlRand=new ThreadLocal(() => { var x=Interlocked.Add(ref seedUnique, 93459872); var r=new Random((int)(DateTime.UtcNow.Ticks + x)); return r; }); public static int SafeRandom(int min, int max) { return tlRand.Value.Next(min,max); } 

它是Random线程安全。

一旦我调用Random.Next()线程安全,我就按预期得到了以下分布。

 2: 2.76665 3: 5.5382 4: 8.30805 5: 11.13095 6: 13.8864 7: 16.6808 8: 13.8722 9: 11.14495 10: 8.3409 11: 5.5631 12: 2.76775 public static class Program { private const int Max = 2000000; private static readonly object Lock = new object(); public static void Main() { var r = new Random(); var ints = new int[13]; Parallel.For(0, Max, i => { var result = Rand(r, 1, 7) + Rand(r, 1, 7); Interlocked.Increment(ref ints[result]); }); for (int i = 0; i < ints.Length; i++) { Console.WriteLine("{0}: {1}", i, ints[i] / ((double)Max) * 100); } } private static int Rand(Random random, int minValue, int maxValue) { lock (Lock) { return random.Next(minValue, maxValue); } } }