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