Revisiting randomness
Almost every Stack Overflow question which includes the words "random" and "repeated" has the same basic answer. It's one of the most common "gotchas" in .NET, Java, and no doubt other platforms: creating a new random number generator without specifying a seed will depend on the current instant of time. The current time as measured by the computer doesn't change very often compared with how often you can create and use a random number generator – so code which repeatedly creates a new instance of Random and uses it once will end up showing a lot of repetition.
One common solution is to use a static field to store a single instance of Random and reuse it. That's okay in Java (where Random is thread-safe) but it's not so good in .NET – if you use the same instance repeatedly from .NET, you can corrupt the internal data structures.
A long time ago, I created a StaticRandom class in MiscUtil – essentially, it was just a bunch of static methods (to mirror the instance methods found in Random) wrapping a single instance of Random and locking appropriately. This allows you to just call StaticRandom.Next(1, 7) to roll a die, for example. However, it has a couple of problems:
- It doesn't scale well in a multi-threaded environment. When I originally wrote it, I benchmarked an alternative approach using [ThreadStatic] and at the time, locking won (at least on my computer, which may well have only had a single core).
- It doesn't provide any way of getting at an instance of Random, other than by using new Random(StaticRandom.Next()).
The latter point is mostly a problem because it encourages a style of coding where you just use StaticRandom.Next(…) any time you want a random number. This is undoubtedly convenient in some situations, but it goes against the idea of treating a source of randomness as a service or dependency. It makes it harder to get repeatability and to see what needs that dependency.
I could have just added a method generating a new instance into the existing class, but I decided to play with a different approach – going back to per-thread instances, but this time using the ThreadLocal<T> class introduced in .NET 4.0. Here's the resulting code:
using System;
using System.Threading;
namespace RandomDemo
{
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();
}
public static int Next(int maxValue)
{
return Instance.Next(maxValue);
}
public static int Next(int minValue, int maxValue)
{
return Instance.Next(minValue, maxValue);
}
public static double NextDouble()
{
return Instance.NextDouble();
}
public static void NextBytes(byte[] buffer)
{
Instance.NextBytes(buffer);
}
}
}
The user can still call the static Next(…) methods if they want, but they can also get at the thread-local instance of Random by calling ThreadLocalRandom.Instance – or easily create a new instance with ThreadLocalRandom.NewRandom(). (The fact that NewRandom uses the global instance rather than the thread-local one is an implementation detail really; it happens to be convenient from the point of view of the ThreadLocal<T> constructor. It wouldn't be terribly hard to change this.)
Now it's easy to write a method which needs randomness (e.g. to shuffle a deck of cards) and give it a Random parameter, then call it using the thread-local instance:
public void Shuffle(Random rng)
{
...
}
...
deck.Shuffle(ThreadLocalRandom.Instance);
The Shuffle method is then easier to test and debug, and expresses its dependency explicitly.
Performance
I tested the "old" and "new" implementations in a very simple way – for varying numbers of threads, I called Next() a fixed number of times (from each thread) and timed how long it took for all the threads to finish. I've also tried a .NET-3.5-compatible version using ThreadStatic instead of ThreadLocal<T>, and running the original code and the ThreadStatic version on .NET 3.5 as well.
| Threads | StaticRandom (4.0b2) | ThreadLocalRandom (4.0b2) | ThreadStaticRandom (4.0b2) | StaticRandom(3.5) | ThreadStaticRandom (3.5) |
| 1 | 11582 | 6016 | 10150 | 10373 | 16049 |
| 2 | 24667 | 7214 | 9043 | 25062 | 17257 |
| 3 | 38095 | 10295 | 14771 | 36827 | 25625 |
| 4 | 49402 | 13435 | 19116 | 47882 | 34415 |
A few things to take away from this:
- The numbers were slightly erratic; somehow it was quicker to do twice the work with ThreadStaticRandom on .NET 4.0b2! This isn't the perfect benchmarking machine; we're interested in trends rather than absolute figures.
- Locking hasn't changed much in performance between framework versions
- ThreadStatic has improved massively between .NET 3.5 and 4.0
- Even on 3.5, ThreadStatic wins over a global lock as soon as there's contention
The only downside to the ThreadLocal<T> version is that it requires .NET 4.0 - but the ThreadStatic version works pretty well too.
It's worth bearing in mind that of course this is testing the highly unusual situation where there's a lot of contention in the global lock version. The performance difference in the single-threaded version where the lock is always uncontended is still present, but very small.
Update
After reading the comments and thinking further, I would indeed get rid of the static methods elsewhere. Also, for the purposes of dependency injection, I agree that it's a good idea to have a factory interface where that's not overkill. The factory implementation could use either the ThreadLocal or ThreadStatic implementations, or effectively use the global lock version (by having its own instance of Random and a lock). In many cases I'd regard that as overkill, however.
One other interesting option would be to create a thread-safe instance of Random to start with, which delegated to thread-local "normal" implementations. That would be very useful from a DI standpoint.