Random Numbers in Multithreading

Random Number

The Random number class in the .Net Framework generates random numbers. The Random number class allows creating random number instances by using one of two constructors.
  1. Random(): It uses the system clock as the seed value and creates an instance.

    Random uses the System clock as input when creating two instances as in the following:
    1. Random rand = new Random();  
    2. Random rand1 = new Random();  
    It uses the same system clock input so the output of the preceding from the following code:
    1. Console.WriteLine(rand.Next());  
    2. Console.WriteLine(rand1.Next());  
    Generates the same random number. Both lines of code in the example writes 10 on the console.

  2. Random(int32 seed): It uses an input integer value and creates an instance.

    Random uses the integer value as input when creating two instance with different input.
    1. Random rand = new Random(30);  
    2. Random rand1 = new Random(40);  
    So the output of the preceding when writing the following code.
    1. Console.WriteLine(rand.Next());  
    2. Console.WriteLine(rand1.Next());  
    Is different random numbers.

The preceding two ways to create a random instance shows that the seed value plays an important role in creating random number instances.

Random in Multi-Threading

The following code can be used as a simulator, in other words the following code can be used to generate something used by another application.

  1. static void Main(string[] args)  
  2. {     
  3.     Program program = new Program();  
  4.       
  5.     char[] publish = new char[] { 'r''v'};  
  6.   
  7.     List<Task> tasks = new List<Task>();  
  8.     while (true)  
  9.     {  
  10.         Console.WriteLine("Press any key to publish new data and 'n' to exit");  
  11.         char rchar = Convert.ToChar(Console.Read());  
  12.         Console.ReadLine();  
  13.         if (rchar == 'n')  
  14.             break;  
  15.   
  16.         foreach (char c in publish)  
  17.         {  
  18.             if (c == 'r')  
  19.                 tasks.Add(Task.Run(() =>program.PublishRateOrVolume(c)));  
  20.             else if (c == 'v')  
  21.                 tasks.Add(Task.Run(() =>program.PublishRateOrVolume(c)));  
  22.         }  
  23.   
  24.         try  
  25.         {  
  26.             Task.WaitAll(tasks.ToArray());  
  27.             foreach (Task<string> t in tasks)  
  28.                 Console.WriteLine(t.Result);  
  29.         }  
  30.         catch (AggregateException ae)  
  31.         {  
  32.             Console.WriteLine(ae.ToString());  
  33.         }  
  34.         tasks.Clear();  
  35.     }  
  36.     tasks = null;  
  37. }  
The preceding code creates two tasks and one task generates a rate data and the other task generates volume data for publishing data. Things to note in the preceding code is that both tasks call the same method but with different input values for "r" (rate) and "v" (volume).
  1. private string PublishRateOrVolume(char publishChar)  
  2. {  
  3.    StringBuilder sb = new StringBuilder();  
  4.    char[] inputchar = new char[] { '1''2'};  
  5.    string clientName = string.Empty;  
  6.   
  7.    var random = new Random();  
  8.   
  9.    try  
  10.    {  
  11.       foreach (char c in inputchar)  
  12.       {  
  13.          if (c == '1')  
  14.          clientName = "Test 1";  
  15.          if (c == '2')  
  16.          clientName = "Test 2";  
  17.   
  18.          var data = random.NextDouble() * random.Next(0, 500);  
  19.   
  20.          if (publishChar == 'v')  
  21.          sb.AppendLine("VOLUME Data Publish by Client :" + clientName + " Volume :" + data);  
  22.          else if (publishChar == 'r')  
  23.          sb.AppendLine("RATE Data Publish by Client :" + clientName + " Rate :" + data);  
  24.       }  
  25.       return sb.ToString();  
  26.    }  
  27.    finally  
  28.    {  
  29.       random = null;  
  30.       inputchar = null;  
  31.       sb = null;  
  32.    }  
  33. }  
The preceding code function is called by a different task with respective arguments to generate volume and rate data. Each task generates volume and rate data for two clients (test 1 and test 2). Things to note is that the code function creates a Random class instance for generating the random data.

The preceding code generates the following output:

output

The problem with the output is that Program generates the same data for volume and rate for each client that is not expected, the expected result is for it to generate multiple random values for rate and value for each client.

The issue

The issue with the preceding code is that a random number instance is created by two different tasks almost with the same seed value, since the seed value for both random instances is the same, it generates the same values. It's already discussed in the preceding in the random class constructor example.

The following is the solution to the problem of random numbers in a multithreading environment. 
  1. Add Delay between creating two task

    Since Random instances use the system clock value, adding a delay between the creation of two tasks provides a different seed value to the default random class instance. So the code for this is:
    1. foreach (char c in publish)  
    2. {  
    3.    if (c == 'r')  
    4.    tasks.Add(Task.Run(() =>program.PublishRateOrVolume(c)));  
    5.    else if (c == 'v')  
    6.    tasks.Add(Task.Run(() =>program.PublishRateOrVolume(c)));  
    7.    Thread.Sleep(50);  
    8. }  
    With the Thread.Sleep a small delay occurs between the creation of the task and since there is a delay the two instance of random numbers receive a different seed.

    But the problem with this solution is it requires the addition of a delay, so if there are more than two tasks then it will create a problem, so it also causes a problem in mission-critical applications where time is important.

  2. Use only one instance of the Random class

    With this solution only one instance of the random number class is created and shared among multiple threads. So the code is:
    1. static void Main(string[] args)  
    2. {  
    3.    Random random = new Random();  
    4.    // code as it is  
    5.    foreach (char c in publish)  
    6.    {  
    7.       if (c == 'r')  
    8.       tasks.Add(Task.Run(() => program.PublishRateOrVolume(c, random)));  
    9.       else if (c == 'v')  
    10.       tasks.Add(Task.Run(() => program.PublishRateOrVolume(c, random)));  
    11.    }  
    12.   
    13.    //code as it is  
    14. }  
    15. private string PublishRateOrVolume(char publishChar, Random random)  
    16. {  
    17.    //code as it is   
    18. }  
    As in this code one random instance is created in the main method and the same instance is used by both tasks to generate random numbers.

    But the problem with this solution is that one random number instance is shared between two tasks and the random number class is not thread safe.

    Since the random number instance is not thread-safe when two threads call the next() method at the same time it will generate 0 as output and then the random number generates 0 and is not useful. To check try the following example:
    1. Random rand = new Random();  
    2.   
    3. Parallel.For(0, 1000000, (i, loop) =>  
    4. {  
    5.    if (rand.Next() == 0) loop.Stop();  
    6. });  
    To avoid that problem use a lock when accessing a random instance via it methods like next(), netxtDouble().
    1. object lockobj = new object();  
    2. lock (lockobj)  
    3. {  
    4.    data= rand.NextDouble() * rand.Next(0, 500);  
    5. }  
    It resolves issues but the problem is a lock statement slows down the application when there are more threads.

  3. Make use of ThreadLocal<T> with the different seed value

    This solution uses the ThreadLocal<T> class, it allows creating local variables for each thread/task. Read more about ThreadLocal class.

    So the code with ThreadLocal<T> is as in the following:
    1. private string PublishRateOrVolume(char publishChar)  
    2. {  
    3.    var random = new ThreadLocal<Random>(() => new Random(Guid.NewGuid().GetHashCode()));  
    4.    //code as it is   
    5.    var data = random.Value.NextDouble() * random.Value.Next(0, 500);   
    6.    //code as it is  
    7. }  
    In that code the random instance is created using the ThreaLocal<T> class, in other words each task receives its own local instance of the Random class.

    Another thing to note in the code is the seed value passed when creating the Random instance. The Seed value is the HashCode value of the Guid instance, in other words each time a new instance receives a new seed value.

Conclusion

The Random class is not thread-safe and the preceding shows three solutions to make it thread-safe but out of the three the third solution is perfect and is the best fit as a thread-safe solution.

Please find the attached code with all three solutions.


Similar Articles