Random Number in MultiThreading
Random Number
Random number class in .net framework used for generating random numbers. Random number class allows creating random number instance by following two constructors-
Random() – it make use of system clock as seed value and create instance.
As random make use of System clock as input when creating two instances as belowRandom rand = new Random(); Random rand1 = new Random();
It use same system clock input so output of above when doing below codeConsole.WriteLine(rand.Next()); Console.WriteLine(rand1.Next());
generate same random number. Example both line of code write 10 on console.
-
Random(int32 seed) – it make use of input integer value and create instance.
As random make use integer value as input when creating two instance with different inputRandom rand = new Random(30); Random rand1 = new Random(40);
so output of above when writing below codeConsole.WriteLine(rand.Next()); Console.WriteLine(rand1.Next());
Generate different random number.
Random In Multi-Threading
Following is line of code that used as simulator i.e following line of code used to generate which 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; }
Above line 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 above code is both the task calling 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; } }
Above code function, 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.
Above line of code generate following output
Problem with the output is Program generate same data for volume and rate for each client which is not expected, expected result is it generate different random values of rate and value for each client.
What is the issue?
Issue with above code is random number instance get created by two different task almost with the same seed value, as the seed value for both random instance is same it generate same values. It’s already discussed above in random class constructor example.Following is solution to the problem of random number in multithreading environment
- Add Delay between creating two task
As random instance make use of system clock value adding delay between creation of two task gives different seed value to default random class instance.
So the code for this is
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 is get added between creating of task and as there is delay two instance of random number receive different seed.
But problem with this solution is it requires to add delay, so if there are more than two task it will create problem, also it cause 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 get created and shared between multiple threads. So the code isstatic 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 in main method and same instance used by both task to generate random number.
But 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 thread call next() method at same time it will generate 0 as output and then afterwards random number generates 0 and not useful. To check try below example
Random rand = new Random(); Parallel.For(0, 1000000, (i, loop) => { if (rand.Next() == 0) loop.Stop(); });
To avoid problem make use of lock when accessing random instance via it methods like next(), netxtDouble().
object lockobj = new object(); lock (lockobj) { data= rand.NextDouble() * rand.Next(0, 500); }
It resolves issue but problem is lock statement slow down application when there are more number of threads.
-
Make use of ThreaLocal<T> with the different seed value
This solution make use of ThreaLocal<T> class, it allows creating local variable for each thread/task. Read more about ThreadLocal class : https://msdn.microsoft.com/en-us/library/dd642243%28v=vs.110%29.aspx
So the code with ThreaLocal<T> as belowprivate 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 ThreaLocal<T> class means each task receive its own local instance of Random class.
Another thing to note in code is seed value passed when creating Random instance. Seed value is HashCode value of the Guid instance i.e. each time new instance receives new seed value.
Conclusion
Random class is not thread safe and above is three solution to make it thread safe but out of three the third solution is perfect and fits as thread safe solution.Please find attached code which having all three solution.
No comments:
Post a Comment