Parallel.For and For yield different results

前端 未结 3 1896
春和景丽
春和景丽 2021-02-13 15:53

If I run this test:

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


        
相关标签:
3条回答
  • 2021-02-13 16:24

    In addition to @spencerruport's assertion that the Random class is not thread safe, your parallel code is also not threadsafe:

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

    It might be better to leverage PLINQ to do the collecting of results on your behalf:

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

    instead of managing access to shared memory (your ints array above) yourself.

    A reasonable implementation of SafeRandom might look something like this:

    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);
    }
    
    0 讨论(0)
  • 2021-02-13 16:43

    The Random class methods are not thread safe.

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

    So the first piece of code is just demonstrating some undefined behavior.

    EDIT:

    As for a little speculation, from what little I know about operating systems I believe random number generation is a pretty low level operation and hence might even require a context switch. While this is happening you may end up grabbing the same random number multiple times before it's had a chance to update. This would account for the lopsided distribution.

    0 讨论(0)
  • 2021-02-13 16:45

    It is thread safety of Random.

    I get the below distribution as expected once I've made the call to Random.Next() thread safe.

    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);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题