zoukankan      html  css  js  c++  java
  • 用线程安全随机数解决Random在多线程中随机性重复的问题

    在.NET中,随机数一般是用Random来获取,但是当在多任务的并行化编程时,问题就出现了。因为Random是基于时间作为种子来生成伪随机数的,而如果程序在多核并行时,在同一时间内的多个核中取到的时间是一样的,这样一来,生成的伪随机数就有可能会有一样的。如果业务需求中需要不可重复的随机数,那么这后果将会相当严重,所以必须采取一种新的方式来获取线程安全的伪随机数。下面是摘自《.NET Parallel Extensions》中的一段关于线程安全随机数生成的类,也可参看http://code.msdn.microsoft.com/Samples-for-Parallel-b4b76364/sourcecode?fileId=44488&pathId=1352203765

     public class ThreadSafeRandom : Random
        {
            //This is the seed provider
            private static readonly RNGCryptoServiceProvider _global = new RNGCryptoServiceProvider();
    
            //This is the provider of randomness.
            //There is going to be one instance of Random per thread
            //because it is  declared as ThreadLocal<Random>
            private ThreadLocal<Random> _local = new ThreadLocal<Random>(() =>
            {
                //This is the valueFactory function
                //This code will run for each thread to initialize each independent instance of Random.
                var buffer = new byte[4];
                //Calls the GetBytes method for RNGCryptoServiceProvider because this class is thread-safe
                //for this usage.
                _global.GetBytes(buffer);
                //Return the new thread-local Random instance initialized with the generated seed.
                return new Random(BitConverter.ToInt32(buffer, 0));
            });
    
            public override int Next()
            {
                return _local.Value.Next();
            }
    
            public override int Next(int maxValue)
            {
                return _local.Value.Next(maxValue);
            }
    
            public override int Next(int minValue, int maxValue)
            {
                return _local.Value.Next(minValue, maxValue);
            }
    
            public override double NextDouble()
            {
                return _local.Value.NextDouble();
            }
    
            public override void NextBytes(byte[] buffer)
            {
                _local.Value.NextBytes(buffer);
            }
        }
    这个类ThreadSafeRandom继承自Random,所以可以像Random一样使用。

    这里边关键用到了几个技术点:

    1、RNGCryptoServiceProvider的加密随机生成器,再用其中的强随机序列的方法GetBytes来实现随机。

    2、使用ThreadLocal来懒惰初使化(Lazy-Initialize)随机数的实例。因为ThreadLocal是针对于每一个线程的线程安全类,是线程的本地存储形式。如果同一个线程多次初始化ThreadLocal,那么得到的实例将会是一样的。因为如果一个线程已经初始化了该实例之后( ThreadSafeRandom safeRandom = new ThreadSafeRandom()),该线程后面继续初始化(再次调用 ThreadSafeRandom safeRandom = new ThreadSafeRandom())是不会再初始化一次,而是会返回之前的实例(有点像单件模式)。不过,这也带来了另一个问题,如果就是要在线程中不断产生新的实例时,这种做法就变的不合适了,不悉采用变通或者其他做法。

    下面是关于Random和ThreadSafeRandom测试的实例

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Chapter11_Console
    {
        public class RadomTest
        {
            #region Run Function
            public static void Run()
            {
                Console.WriteLine("Started!");
    
                var sw = Stopwatch.StartNew();
                int normalRandomSameCount = randomSerial(generateNormalRadoms);
                Console.WriteLine("Normal Random Same Count:{0}, Consume Time:{1}", normalRandomSameCount, sw.Elapsed.ToString());
    
                sw.Restart();
                int threadSafeRandomSameCount = randomSerial(generateThreadSafeRadoms);
                Console.WriteLine("Thread Safe Random Same Count:{0}, Consume Time:{1}", threadSafeRandomSameCount, sw.Elapsed.ToString());
    
                Console.WriteLine("Completed!");
                Console.ReadLine();
            }
    
            private static int randomSerial(Func<int, List<int>> generateRadoms)
            {
                int randomCount = 100000;
                Task<List<int>>[] tasks = new Task<List<int>>[2];
                for (int i = 0; i < tasks.Length; i++)
                {
                    tasks[i] = Task.Factory.StartNew(() =>
                    {
                        return generateRadoms(randomCount);
                    });
                }
                Task.WaitAll(tasks);
                int sameCount = 0;
                Task finalTask = Task.Factory.StartNew(() =>
                {
                    for (int i = 0; i < randomCount; i++)
                    {
                        if (tasks[0].Result[i] == tasks[1].Result[i])
                        {
                            sameCount++;
                        }
                    }
                });
                finalTask.Wait();
                return sameCount;
            }
    
            private static List<int> generateNormalRadoms(int randomCount)
            {
                List<int> randoms = new List<int>();
                for (int i = 0; i < randomCount; i++)
                {
                    Random random = new Random();
                    randoms.Add(random.Next());
                }
                return randoms;
            }
    
            private static List<int> generateThreadSafeRadoms(int randomCount)
            {
                List<int> randoms = new List<int>();
                for (int i = 0; i < randomCount; i++)
                {
                    ThreadSafeRandom safeRandom = new ThreadSafeRandom();
                    randoms.Add(safeRandom.Next());
                }
                return randoms;
            }
            #endregion
        }
    }
    

    运行结果如下图



    多运行几次会发现,有的时候Normal Random Same Count也有为0的时候,有的时候会很小,有的时候会很大,具体多少是随机性的。

    注意:

       1、测试时必须是多核的。以上程序是在双核上运行的,如果有四核或更多的核,可以将任务数加大。

       2、线程安全虽然确保了随机数的安全性,但是会消耗更多时间。

    转载请注明出处http://blog.csdn.net/xxdddail/article/details/16980743

  • 相关阅读:
    Linux系统教程:设置GRUB菜单密码
    vimdiff的常用命令
    Zero-Copy实现原理
    解决业务代码里的分布式事务一致性问题
    用好这6个APP,学英语SO EASY!
    线程池调优
    理解select,poll,epoll实现分析
    时序图
    性能监控-TP理解
    sshd_config OpenSSH SSH 进程配置文件配置说明
  • 原文地址:https://www.cnblogs.com/sparkleDai/p/7605067.html
Copyright © 2011-2022 走看看