I haven't written a blog for a long time. Today I want to write a question about thread security. I can clearly understand from my previous blog that I am opposed to using the singleton mode. Here I just want to give a very simple example to illustrate that singleton may cause more serious problems than we think.
The main reason why I am opposed to using singleton is that singleton providers often fail to implement thread security well, either by recognizing thread security or simply thinking that thread security is irrelevant.
So what are the issues with writing code with a thread that is not secure?
Example 1 -- Random
Let's take a look at this Code:
1 using System; 2 using System.Threading; 3 4 namespace NotThreadSafe 5 { 6 class Program 7 { 8 static volatile bool s_running = true; 9 static CountdownEvent s_event = new CountdownEvent(2);10 static Random s_random = new Random();11 12 static void Main(string[] args)13 {14 ThreadPool.QueueUserWorkItem(DoRandomHeavily);15 ThreadPool.QueueUserWorkItem(DoRandomHeavily);16 Thread.Sleep(1000);17 s_running = false;18 s_event.Wait();19 Console.WriteLine(s_random.Next());20 }21 22 static void DoRandomHeavily(object _)23 {24 while (s_running)25 s_random.Next();26 s_event.Signal();27 }28 }29 }
View Code
Let's guess what the output of this code is.
I guess most people will say the result is 0 ~ Int. MaxValue, but in fact, if the result of this code running on a multi-core computer (at least 99.9% of the probability) is 0. Is it unexpected?
Why ?!
To see why the result is so strange, first of all, please refer to msdn. msdn clearly shows that the Random type does not guarantee the thread security of instance members. Therefore, it is incorrect to use random as in the code above, which may cause some problems.
Many people think that the role of random is to give a number at will, and it doesn't matter if the thread is not secure.
However. net designs random as a class that can repeatedly generate a sequence through seed. Although this sequence looks very random, it is actually still an algorithm, and there are algorithms that are not a problem, the problem is that this algorithm depends on some instance fields. Through decompilation, we can find that random has three important strength fields: an int [] used to store a bunch of numbers; two intors, it is used to determine the two numbers in the array for a series of operations (the core is a subtraction ).
Obviously, reading two int fields and writing two int fields are not Atomic. Therefore, in a multi-threaded environment, it is likely that the first int field is read from the local cache value of the current cpu, and the cache of the current cpu is refreshed before the second int field is read, the read value is changed to the value written from other CPUs. Although this does seem to have changed the sequence that should be included in the random, it does not affect our purpose-random. But don't forget that this change can make the values of the two int fields the same, and this probability is already as high as that in mathematics and cannot be called a small probability!
Once the two int fields change to the same value, the nightmare begins. The two times are retrieved from the same position of the array, so it is the same under the probability of> 99% (<1% is about multi-thread problems, it's about your shit ...), The two numbers are reduced to 0, and a series of operations are performed. The operations are still 0, and then saved to the array. The array is flushed to 0 if there is no multithreading problem in the middle, it doesn't matter if the next two int values are so casual, because 0 minus 0 must be 0.
Not enough? Example 2-Dictionary
Let's look at the Code:
1 using System; 2 using System.Collections.Generic; 3 using System.Threading; 4 5 namespace NotThreadSafe 6 { 7 class Program 8 { 9 static volatile bool s_running = true;10 static CountdownEvent s_event = new CountdownEvent(2);11 static Dictionary<int, int> s_dict = new Dictionary<int, int>();12 13 static void Main(string[] args)14 {15 ThreadPool.QueueUserWorkItem(DoItHeavily);16 ThreadPool.QueueUserWorkItem(DoItHeavily);17 Thread.Sleep(1000);18 s_running = false;19 s_event.Wait();20 Console.WriteLine(s_dict.Count);21 }22 23 static void DoItHeavily(object _)24 {25 while (s_running)26 {27 s_dict[1] = 1;28 s_dict.Remove(1);29 }30 s_event.Signal();31 }32 }33 }
View Code
What do you think will be output?
People who do not understand the above may say 0. People who have understood the above may say that the Program Reports errors and crashes, but the facts are always surprising, running on a multi-core machine usually results in the program being unable to exit and directly occupying the computing resources of two cores (that is, the dual-core is 100% occupied, and the 4-core is 50% occupied ...)
To put it simply, the internal data structure of Dictionary is damaged by multiple threads, leading to an endless loop during the Add operation. If you want to explain it in a complicated way, capture dump on your own.
I will not give more examples. I will go back to msdn and check the thread security. It is not hard to find that 99% of the types provided by Framework for us do not guarantee the thread security of instance members, almost only a few types will write thread security, and most of the above are the lock types used to process thread security. So for the singleton providers, check whether the code is thread security? Are all locked? If thread security cannot be ensured, the code will collapse instantly within one second as long as the multi-threaded environment and heavy is used.
PS: the code section uses. for example, but do not think that only. net, method -- singleton, ask the reason, saving memory...