3

This question comes from the knowledge that when new Random() is called very quickly it gets seeded with the same value which I assume is based on DateTime.Now.Ticks.

Suppose you had a high traffic web application on latest versions ASP.NET, IIS, .NET, etc.. which implements an online casino. I would think the simulated slot machines need to pull from a single random number stream.

With high volume situation you might get two slot machines with the same random number generator causing too many big jackpots. I don't have a full understanding of pseudorandom generators but my intuition is that to safely implement an online casino you really need to pull from a single generator that is seeded just once.

One solution I can think of is a synchronized queue with a single thread that pushes the numbers on, but I wouldn't know how to synchronize it across a multi-site application.

Is there a good/standard solution to this scenario?

Update: the actual scenario I'm working on (which really has no chance of getting enough volume to cause a problem)

My real-world situation is that I have a moderately high traffic asp.net web forms site and the requirement to show one of two user controls on each request. I did so with the following code:

// Randomly display one of (FreeCreditScore1, MyFreeScoreNow1)
private void ShowCreditScoreAd()
{
    FreeCreditScore1.Visible = (new Random().Next(2) == 1);
    MyFreeScoreNow1.Visible = !FreeCreditScore1.Visible;
}

If I unit test the above code by calling it in rapid succession it fails and I came to understand the reason was that new Random() was getting called in the same "tick".

That said, I do believe my current implementation is sufficient (feel free to correct me) but I was curious how it could be solved if I really wanted to be strict about it...

Aaron Anodide
  • 16,906
  • 15
  • 62
  • 121
  • 1
    "Suppose you had a high traffic web application on latest versions ASP.NET, IIS, .NET, etc.. which implements an online casino." - then you should be using a physical source of random noise. Period. Please search SO. "to safely implement an online casino" you need a physical noise source. – Mitch Wheat Feb 20 '13 at 03:41
  • that's interesting - I never heard of that approach. will search. I'm not making a casino though - just the only example I could think of that might have enough traffic to cause two requests in the same tick – Aaron Anodide Feb 20 '13 at 03:43
  • Interesting theoretical question...for your actual purpose (i.e. "show one or the other") you could enforce this across servers using the database as a common ground to handle concurrency/locking. Something as simple as the modulus of an auto-incremented ID could work, or you could get more elaborate with a SQL application lock. – Tim M. Feb 20 '13 at 04:27
  • Of course, using the database is now out of the RNG realm, unless you chose to use it as a common ground for keeping track of seeds (or something else RNG-related). – Tim M. Feb 20 '13 at 04:28
  • I used similar logic to pull 3 random images (out of about 8 images) on page load. The majority of page loads always showed the same 3 images. Gooooo Random(). – MikeSmithDev Feb 20 '13 at 04:35
  • @TimMedora, that's a creative solution which I do like (inserting into a sql table with identity increment), thanks! – Aaron Anodide Feb 20 '13 at 04:50
  • Why worry about synchronizing the numbers across machines? If you use something like the RNGCryptoServiceProvider on each machine, you're not going to end up with two pseudorandom number generators giving the same sequence of numbers. – Jim Mischel Feb 20 '13 at 04:54
  • @JimMischel, thanks - I'll definitely look at that - honestly, I had never heard of RNGCryptoServiceProvider.... – Aaron Anodide Feb 20 '13 at 07:23

2 Answers2

2

Just protect the random number generator with a lock. Unless you're making hundreds of thousands of calls per second to it, it's going to be plenty fast enough.

public class MyRandomObject
{
    private readonly Random _rnd = new Random();

    public int Next()
    {
        lock (_rnd)
        {
            return _rnd.Next();
        }
    }
}

Before you say, "locks are too slow," note that I tested this on an old 2.4 GHz quad core. It took approximately 70 nanoseconds to get a random number when the lock isn't contended. Lock contention can kill performance, but you'd need lots of requests.

In a Web app, you'd want to initialize one of those in Application_Start, and make that singleton available to the rest of your application.

There are faster ways, but they're more difficult to implement. One way would be to pre-generate millions of random numbers (call Random.NextBytes) and store them in a buffer. Protect that with a reader/writer lock. When the number of values remaining reaches some threshold, the thread grabs the writer lock, preventing all other access until it refills the buffer. This is probably the way you'd want to go if you're using some other source of random numbers. For example the RNGCryptoServiceProvider.GetBytes method (http://msdn.microsoft.com/en-us/library/system.security.cryptography.rngcryptoserviceprovider.getbytes.aspx).

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • thanks for the answer and insights... good for the non-web-farm case. `lock` doesn't cross machine boundary though – Aaron Anodide Feb 20 '13 at 04:39
  • @AaronAnodide: No, `lock` won't cross machine (or even process) boundaries. But you could have one machine that is the random number server. Other machines get blocks of numbers from it (megabytes at a time). Most of the time, the machines act independently, only having to visit the random number server when they run out of numbers. – Jim Mischel Feb 20 '13 at 04:51
0

Create one static Random instance. Seed it by generating 4 cryptographically secure random bytes. You can even make that Random instance [ThreadLocal] to remove all locking.

I don't know why the BCL does not contain such a thing and uses it automatically to seed new instances. It is so easy to do, provides perfect seeding and scales perfectly to an arbitrary number of threads.

usr
  • 168,620
  • 35
  • 240
  • 369