//ThreadSafeRandom.cs
using System;
using System.Security.Cryptography;
using System.Threading;
namespace Utility
//namespace System.Threading
{
public class ThreadSafeRandom : Random
{
// seed provider
private static readonly RNGCryptoServiceProvider _global = new RNGCryptoServiceProvider();
// provider of randomness
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);
}
}
}
测试:
//Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Diagnostics;
namespace Test
{
class Program
{
static void Main(string[] args)
{
TestThreadSafeRandom();
}
private static void TestThreadSafeRandom()
{
//普通Random出现大量重复
TestThreadSafeRandom0();
//统计上看:普通Random不如ThreadSafeRandom稳定
TestThreadSafeRandom1();
}
private static void TestThreadSafeRandom0()
{
int taskCount = 4;
int randomCount = 100000;
var sw = Stopwatch.StartNew();
var normalRandomSameCount = GenerateAndStatSameCount(taskCount, randomCount, GenerateNormalRandoms);
Console.WriteLine("Normal random same count:{0}, Consume time:{1}", normalRandomSameCount, sw.Elapsed.ToString());
sw.Restart();
var threadSafeRandomSameCount = GenerateAndStatSameCount(taskCount, randomCount, GenerateThreadSafeRandoms);
Console.WriteLine("Thread safe random same count:{0}, Consume time:{1}", threadSafeRandomSameCount, sw.Elapsed.ToString());
}
private static int GenerateAndStatSameCount(int taskCount, int randomCount, Func<int, List<int>> generateRandoms)
{
Task<List<int>>[] tasks = new Task<List<int>>[taskCount];
for (int i = 0, length = tasks.Length; i < length; i++)
{
tasks[i] = Task.Factory.StartNew(() =>
{
return generateRandoms(randomCount);
});
}
Task<int> finalTask = Task.Factory.ContinueWhenAll(tasks, (ts) =>
{
int sameCount = 0;
List<int>[] ls = new List<int>[ts.Length];
for (int i = 0, length = ls.Length; i < length; i++)
{
ls[i] = ts[i].Result;
}
for (int i = 0, length = randomCount; i < length; i++)
{
if (IsSameValueAtPostion(ls, i))
{
sameCount++;
}
}
return sameCount;
});
finalTask.Wait();
return finalTask.Result;
}
private static bool IsSameValueAtPostion(List<int>[] ls, int pos)
{
bool result = true;
for (int i = 0, length = ls.Length - 1; i < length; i++)
{
if (ls[i][pos] != ls[i + 1][pos])
{
result = false;
break;
}
}
return result;
}
private static List<int> GenerateNormalRandoms(int randomCount)
{
List<int> randoms = new List<int>(randomCount);
for (int i = 0; i < randomCount; i++)
{
Random rand = new Random();
randoms.Add(rand.Next());
}
return randoms;
}
private static List<int> GenerateThreadSafeRandoms(int randomCount)
{
List<int> randoms = new List<int>(randomCount);
for (int i = 0; i < randomCount; i++)
{
Utility.ThreadSafeRandom rand = new Utility.ThreadSafeRandom();
randoms.Add(rand.Next());
}
return randoms;
}
private static void TestThreadSafeRandom1()
{
int taskCount = 4;
int randomCount = 100000;
Tuple<int, int> randomRange = new Tuple<int, int>(0, 10);
Random normalRandom = new Random();
Random threadSafeRandom = new Utility.ThreadSafeRandom();
ConcurrentBag<int> numberGeneratedByNormalRandomParallel = new ConcurrentBag<int>();
ConcurrentBag<int> numberGeneratedByThreadSafeRandomParallel = new ConcurrentBag<int>();
var sw = Stopwatch.StartNew();
GenerateRandomNumbersTasks(taskCount, randomCount, randomRange.Item1, randomRange.Item2, normalRandom, numberGeneratedByNormalRandomParallel);
Console.WriteLine("rand type:{0}", normalRandom.GetType().Name);
StatEachRandomNumberPercentage(randomRange.Item1, randomRange.Item2, numberGeneratedByNormalRandomParallel);
sw.Restart();
GenerateRandomNumbersTasks(taskCount, randomCount, randomRange.Item1, randomRange.Item2, threadSafeRandom, numberGeneratedByThreadSafeRandomParallel);
Console.WriteLine("rand type:{0}", threadSafeRandom.GetType().Name);
StatEachRandomNumberPercentage(randomRange.Item1, randomRange.Item2, numberGeneratedByThreadSafeRandomParallel);
}
private static void StatEachRandomNumberPercentage(int minValue, int maxValue, ConcurrentBag<int> bag)
{
int count = 0;
int total = bag.Count;
var groups = bag.ToLookup(e => e, e => 1);
for (int i = minValue; i < maxValue; i++)
{
var group = groups[i];
count = 0;
if (group != null)
{
count = group.Count();
}
Console.WriteLine("\t{0} percentage:{1}", i, count / (double)total);
}
}
private static void GenerateRandomNumbersTasks(int taskCount, int randomCount, int minValue, int maxValue, Random rand, ConcurrentBag<int> bag)
{
int perCount = randomCount / taskCount;
Task[] tasks = new Task[taskCount];
int length = taskCount - 1;
for (int i = 0; i < length; i++)
{
tasks[i] = Task.Factory.StartNew(() =>
GenerateRandomNumbers(perCount, minValue, maxValue, rand, bag));
}
tasks[length] = Task.Factory.StartNew(() =>
GenerateRandomNumbers(randomCount - perCount * length, minValue, maxValue, rand, bag));
Task.WaitAll(tasks);
}
private static void GenerateRandomNumbers(int randomCount, int minValue, int maxValue, Random rand, ConcurrentBag<int> bag)
{
for (int i = 0; i < randomCount; i++)
{
int num = rand.Next(minValue, maxValue);
bag.Add(num);
}
//Parallel.For(0, randomCount, index =>
//{
// bag.Add(rand.Next(minValue, maxValue));
//});
}
}
}
CPU:四核,8逻辑处理器(使用超线程技术(Inter Hyper-Threading Technology),每个物理内核提供两份架构状态,获得8个硬件线程)(听着高大上,还没有工作用的单纯四核快;上面代码测试时,开4个任务要比开8个任务快)
2组测试结果:
1组:
Normal random same count:0, Consume time:00:00:00.3157778
Thread safe random same count:0, Consume time:00:00:32.5139927
rand type:Random
0 percentage:0.1206
1 percentage:0.09818
2 percentage:0.09882
3 percentage:0.09959
4 percentage:0.09695
5 percentage:0.09931
6 percentage:0.09772
7 percentage:0.09733
8 percentage:0.09627
9 percentage:0.09523
rand type:ThreadSafeRandom
0 percentage:0.09955
1 percentage:0.10108
2 percentage:0.10031
3 percentage:0.1009
4 percentage:0.1
5 percentage:0.09961
6 percentage:0.09932
7 percentage:0.09972
8 percentage:0.1
9 percentage:0.09951
2组:
Normal random same count:25988, Consume time:00:00:00.2909146
Thread safe random same count:0, Consume time:00:00:32.5904478
rand type:Random
0 percentage:0.69254
1 percentage:0.03408
2 percentage:0.03349
3 percentage:0.03364
4 percentage:0.03416
5 percentage:0.03373
6 percentage:0.03454
7 percentage:0.03422
8 percentage:0.03438
9 percentage:0.03522
rand type:ThreadSafeRandom
0 percentage:0.09986
1 percentage:0.10144
2 percentage:0.10077
3 percentage:0.09944
4 percentage:0.10002
5 percentage:0.09889
6 percentage:0.09934
7 percentage:0.09961
8 percentage:0.10047
9 percentage:0.10016
从第二组结果可以看出,并行情况下,普通Random生成的随机数有很大的问题:
1.每个任务使用局部变量时,普通Random生成大量相同随机数,这和Random的随机种子有很大的关系;
2.每个任务共享变量时,普通Random生成的随机数分布有很大的问题,这是因为普通Random不是线程安全的,其内部是有状态的,并行情况下,就出现问题了。
共享变量时,加锁或许是一个方法,这里没有测试,后续可以测试下,推测效率上应该没有ThreadSafeRandom高
追加:
普通Random与ThreadSafeRandom的效率对比:
//Program.cs
//class Program
private static void TestThreadSafeRandom2()
{
int taskCount = 4;
int randomCount = 100000;
Tuple<int, int> randomRange = new Tuple<int, int>(0, 10);
Random normalRandom = new Random();
Random threadSafeRandom = new Utility.ThreadSafeRandom();
ConcurrentBag<int> numberGeneratedByNormalRandomParallel = new ConcurrentBag<int>();
ConcurrentBag<int> numberGeneratedByThreadSafeRandomParallel = new ConcurrentBag<int>();
var sw = Stopwatch.StartNew();
GenerateRandomNumbersTasks(taskCount, randomCount, randomRange.Item1, randomRange.Item2, normalRandom, numberGeneratedByNormalRandomParallel, GenerateNormalRandomNumbers);
Console.WriteLine("{0}:{1}", normalRandom.GetType().Name, sw.Elapsed.ToString());
StatEachRandomNumberPercentage(randomRange.Item1, randomRange.Item2, numberGeneratedByNormalRandomParallel);
sw.Restart();
GenerateRandomNumbersTasks(taskCount, randomCount, randomRange.Item1, randomRange.Item2, normalRandom, numberGeneratedByThreadSafeRandomParallel, GenerateThreadSafeRandomNumbers);
Console.WriteLine("{0}:{1}", threadSafeRandom.GetType().Name, sw.Elapsed.ToString());
StatEachRandomNumberPercentage(randomRange.Item1, randomRange.Item2, numberGeneratedByThreadSafeRandomParallel);
}
private static void GenerateRandomNumbersTasks(int taskCount, int randomCount, int minValue, int maxValue, Random rand, ConcurrentBag<int> bag, Action<int, int, int, Random, ConcurrentBag<int>> generate)
{
int perCount = randomCount / taskCount;
Task[] tasks = new Task[taskCount];
int length = taskCount - 1;
for (int i = 0; i < length; i++)
{
tasks[i] = Task.Factory.StartNew(() =>
generate(perCount, minValue, maxValue, rand, bag));
}
tasks[length] = Task.Factory.StartNew(() =>
generate(randomCount - perCount * length, minValue, maxValue, rand, bag));
Task.WaitAll(tasks);
}
private static void GenerateNormalRandomNumbers(int randomCount, int minValue, int maxValue, Random rand, ConcurrentBag<int> bag)
{
int num;
for (int i = 0; i < randomCount; i++)
{
lock (rand)
{
num = rand.Next(minValue, maxValue);
}
bag.Add(num);
}
}
private static void GenerateThreadSafeRandomNumbers(int randomCount, int minValue, int maxValue, Random rand, ConcurrentBag<int> bag)
{
int num;
for (int i = 0; i < randomCount; i++)
{
num = rand.Next(minValue, maxValue);
bag.Add(num);
}
}
private static void TestThreadSafeRandom3()
{
int randomCount = 100000;
Tuple<int, int> randomRange = new Tuple<int, int>(0, 10);
Random normalRandom = new Random();
Random threadSafeRandom = new Utility.ThreadSafeRandom();
int num;
int minValue = randomRange.Item1;
int maxValue = randomRange.Item2;
var sw = Stopwatch.StartNew();
for (int i = 0; i < randomCount; i++)
{
num = normalRandom.Next(minValue, maxValue);
}
Console.WriteLine("{0}:{1}", normalRandom.GetType().Name, sw.Elapsed.ToString());
sw.Restart();
for (int i = 0; i < randomCount; i++)
{
num = threadSafeRandom.Next(minValue, maxValue);
}
Console.WriteLine("{0}:{1}", threadSafeRandom.GetType().Name, sw.Elapsed.ToString());
}
//Program.cs
//Program.TestThreadSafeRandom
private static void TestThreadSafeRandom()
{
//普通Random出现大量重复
//TestThreadSafeRandom0();
//统计上看:普通Random不如ThreadSafeRandom稳定
//TestThreadSafeRandom1();
//并行:普通Random+lock 不如 ThreadSafeRandom 快
TestThreadSafeRandom2();
//串行:普通Random 比 ThreadSafeRandom 快
TestThreadSafeRandom3();
}
一组测试输出:
Random:00:00:00.0368598
0 percentage:0.09868
1 percentage:0.09849
2 percentage:0.10021
3 percentage:0.10048
4 percentage:0.10172
5 percentage:0.10031
6 percentage:0.1011
7 percentage:0.09857
8 percentage:0.10067
9 percentage:0.09977
ThreadSafeRandom:00:00:00.0058398
0 percentage:0.09968
1 percentage:0.09933
2 percentage:0.09975
3 percentage:0.09915
4 percentage:0.1003
5 percentage:0.09972
6 percentage:0.10234
7 percentage:0.10029
8 percentage:0.09897
9 percentage:0.10047
Random:00:00:00.0021268
ThreadSafeRandom:00:00:00.0055171
不难得出结论:并行情况下,普通Random需要加锁,这样效率上就不如ThreadSafeRandom了;串行情况下,没有加锁需求,普通Random是比ThreadSafeRandom快的。