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

Dou*_* M. 13 c# parallel-processing multithreading c#-4.0

如果我运行此测试:

 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;
 });
Run Code Online (Sandbox Code Playgroud)

我得到以下结果:

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
Run Code Online (Sandbox Code Playgroud)

当我使用常规For:

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

输出是:

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
Run Code Online (Sandbox Code Playgroud)

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

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

Spe*_*ort 26

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

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

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

编辑:

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


spe*_*der 9

除了@ 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;
 });
Run Code Online (Sandbox Code Playgroud)

利用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()})
Run Code Online (Sandbox Code Playgroud)

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

合理的实现SafeRandom可能看起来像这样:

private static int seedUnique=0;
private static ThreadLocal<Random> tlRand=new ThreadLocal<Random>(() => {
    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);
}
Run Code Online (Sandbox Code Playgroud)