Random Number in MultiThreading






4.76/5 (8 votes)
Random number in MultiThreading
Random Number
Random number class in .NET Framework is used for generating random numbers. Random number class allows creating random number instance by following two constructors:
Random()
– It makes use of system clock as seed value and creates instance.As
random
makes use of System clock as input when creating two instances as below:Random rand = new Random(); Random rand1 = new Random();
It use the same system clock input so output of the above when doing below code...
Console.WriteLine(rand.Next()); Console.WriteLine(rand1.Next());
...generates the same random number. Example both lines of code write 10 on console.
Random(int32 seed)
– It makes use of input integer value and creates instance.As
random
makes use of integer value as input when creating two instances with different inputs...Random rand = new Random(30); Random rand1 = new Random(40);
...so output of the above when writing below code:
Console.WriteLine(rand.Next()); Console.WriteLine(rand1.Next());
...generate different random number.
So the above two ways of creating random instance show that seed value plays an important role in creating random number instance.
Random In Multi-Threading
Following are the lines of code that are used as simulator, i.e., following lines of code are used to generate which are used by other application.
static void Main(string[] args)
{
Program program = new Program();
char[] publish = new char[] { 'r', 'v'};
List<Task> tasks = new List<Task>();
while (true)
{
Console.WriteLine("Press any key to publish new data and 'n' to exit");
char rchar = Convert.ToChar(Console.Read());
Console.ReadLine();
if (rchar == 'n')
break;
foreach (char c in publish)
{
if (c == 'r')
tasks.Add(Task.Run(() =>program.PublishRateOrVolume(c)));
else if (c == 'v')
tasks.Add(Task.Run(() =>program.PublishRateOrVolume(c)));
}
try
{
Task.WaitAll(tasks.ToArray());
foreach (Task<string> t in tasks)
Console.WriteLine(t.Result);
}
catch (AggregateException ae)
{
Console.WriteLine(ae.ToString());
}
tasks.Clear();
}
tasks = null;
}
The above lines of code create two tasks in that one task generates rate date and another task generates volume data for publishing data. Things to note in the above code are both the task calling the same method but with the different input value ‘r
’ (rate
) and ‘v
’ (volume
).
private string PublishRateOrVolume(char publishChar)
{
StringBuilder sb = new StringBuilder();
char[] inputchar = new char[] { '1', '2'};
string clientName = string.Empty;
var random = new Random();
try
{
foreach (char c in inputchar)
{
if (c == '1')
clientName = "Test 1";
if (c == '2')
clientName = "Test 2";
var data = random.NextDouble() * random.Next(0, 500);
if (publishChar == 'v')
sb.AppendLine("VOLUME Data Publish by Client :" + clientName + " Volume :" + data);
else if (publishChar == 'r')
sb.AppendLine("RATE Data Publish by Client :" + clientName + " Rate :" + data);
}
return sb.ToString();
}
finally
{
random = null;
inputchar = null;
sb = null;
}
}
The above code function is called by different task with respected argument to generate volume and rate data. Each task generates volume and rate data for two clients (test 1 and test 2). Things to note in code function creates Random
class instance for generating random data.
The above line of code generates the following output:
Problem with the output is that the program generates the same data for volume and rate for each client which is not expected, expected result is it generates different random values of rate and value for each client.
What is the Issue?
The issue with the above code is random number instance gets created by two different tasks almost with the same seed value, as the seed value for both random instance is the same, it generate the same values. It’s already discussed above in random class constructor example.
Following is the solution to the problem of random number in multithreading environment.
- Add delay between creating two tasks:
As random instance makes use of system clock value, adding delay between creation of two tasks gives different seed value to default random class instance. So the code for this is as follows:
foreach (char c in publish) { if (c == 'r') tasks.Add(Task.Run(() =>program.PublishRateOrVolume(c))); else if (c == 'v') tasks.Add(Task.Run(() =>program.PublishRateOrVolume(c))); Thread.Sleep(50); }
With the
Thread.Sleep
, small delay gets added between creating of task and as there is delay, two instances of random number receive different seed.But the problem with this solution is that it requires to add delay, so if there are more than two tasks, it will create a problem, also it causes a problem in mission critical application where time is important.
- Use only one instance of
random
class:According to this solution, only one instance of random number class gets created and shared between multiple threads. So the code is:
static void Main(string[] args) { Random random = new Random(); // code as it is foreach (char c in publish) { if (c == 'r') tasks.Add(Task.Run(() => program.PublishRateOrVolume(c, random))); else if (c == 'v') tasks.Add(Task.Run(() => program.PublishRateOrVolume(c, random))); } } //code as it is } private string PublishRateOrVolume(char publishChar, Random random) { //code as it is }
As in code, one
random
instance is created inmain
method and same instance is used by both tasks to generate random number.But the problem with this solution is one random number instance is shared between two tasks and
random
number class is not thread safe.As
random
number instance is not thread safe when two threads callnext()
method at the same time, it will generate0
as output and then afterwards random number generates0
and is not useful. To check, try the below example:Random rand = new Random(); Parallel.For(0, 1000000, (i, loop) => { if (rand.Next() == 0) loop.Stop(); });
To avoid problems, make use of
lock
when accessing random instance via its methods likenext()
,nextDouble()
.object lockobj = new object(); lock (lockobj) { data= rand.NextDouble() * rand.Next(0, 500); }
It resolves the issue, but the problem is
lock
statement slows down application when there are more number of threads. - Make use of
ThreadLocal<T>
with the different seed value:This solution makes use of
ThreadLocal<T>
class, it allows creating local variable for each thread/task. Read more aboutThreadLocal
class at https://msdn.microsoft.com/en-us/library/dd642243%28v=vs.110%29.aspx.So the code with
ThreadLocal<T>
is as below:private string PublishRateOrVolume(char publishChar) { var random = new ThreadLocal<Random>(() => new Random(Guid.NewGuid().GetHashCode())); //code as it is var data = random.Value.NextDouble() * random.Value.Next(0, 500); //code as it is }
In code, random instance created using
ThreadLocal<T>
class means each task receives its own local instance ofRandom
class.Another thing to note in code is seed value passed when creating
Random
instance. Seed value isHashCode
value of theGuid
instance, i.e., each time new instance receives new seed value.
Conclusion
Random
class is not thread safe and above are three solutions to make it thread safe but out of three, the third solution is perfect and fits as a thread safe solution.
Please find the attached code which has all three solutions.