My program performs many simulations, each of which calls for many random numbers to be generated. The serial method is straightforward and works. However, in my pursuit of parallelizing the work I believe I created a more straightforward method than what I could find. The other methods are somewhat dated and something might be possible now that wasn't possible then.
Am I missing something that will make my method susceptible to any of the myriad of multithreading problems? My method uses the ability of a Parallel.For
to instantiate a variable for individual thread use and thus it doesn't require another class like the other methods I found. In this case each thread gets its own Random
.
Timing:
My method: 4s
Stephen: 14s
Jon: 16s
Clearly I don't know as much as Stephen or Jon so I'm concerned I missed something.
My method:
Random rnd = new Random();
int trials = 1_000_000;
private readonly object globalLock = new object();
ParallelOptions po = new ParallelOptions();
po.MaxDegreeOfParallelism = 4;
await Task.Run(() =>
{
Parallel.For<Random>(0, trials, po,
() => { lock(globalLock){ return new Random(rnd.Next()); } },
(i, loop, local) =>
{
for (int work = 0; work < 1000; work++)
{
local.Next();
}
return local;
},
(x) => { }
);
});
This next method is by Stephen Toub on the MSDN Blog:
public static class RandomGen2
{
private static Random _global = new Random();
[ThreadStatic]
private static Random _local;
public static int Next()
{
Random inst = _local;
if (inst == null)
{
int seed;
lock (_global) seed = _global.Next();
_local = inst = new Random(seed);
}
return inst.Next();
}
}
await Task.Run(() =>
{
Parallel.For(0, trials, i =>
{
for (int work = 0; work < 1000; work++)
{
RandomGen2.Next();
}
});
});
This next method is by Jon Skeet on his blog:
public static class ThreadLocalRandom
{
private static readonly Random globalRandom = new Random();
private static readonly object globalLock = new object();
private static readonly ThreadLocal<Random> threadRandom = new ThreadLocal<Random>(NewRandom);
public static Random NewRandom()
{
lock (globalLock)
{
return new Random(globalRandom.Next());
}
}
public static Random Instance { get { return threadRandom.Value; } }
public static int Next()
{
return Instance.Next();
}
}
await Task.Run(() =>
{
Parallel.For(0, trials, i =>
{
for (int work = 0; work < 1000; work++)
{
ThreadLocalRandom.Instance.Next();
}
});
});
Update/answer: Brian has pointed out that I was using Jon's method incorrectly. A more correct way would be to call an ThreadLocalRandom.Instance
for each Parallel.For
loop and use that instance for the internal for
loop. This prevents the thread check on each call and instead there is only one thread check per Parallel.For
loop. Using Jon's method correctly makes his method faster than the overload of Parallel.For
that I was using.