Recommendation 73: Avoid locking inappropriate synchronization objects
In C #, another way to encode thread synchronization is to use the thread lock. The principle of the thread lock is that it locks a resource so that the application has only one thread to access it at the moment. In layman's words, it makes multithreading a single thread. In C #, a locked resource can be understood as a new normal CLR object.
Since the resource that needs to be locked is an object in C #, we should think carefully about what kind of object can be a lock object (also called a synchronization object). When selecting a synchronization object, you should always be aware of the following points:
1) The synchronization object is the same object that is visible in multiple threads that need to be synchronized.
2) in a non-static method, a static variable should not be used as a synchronization object.
3) A value type object cannot be a synchronization object.
4) Avoid the use of strings as synchronization objects.
5) Reduce the visibility of the synchronization object.
The following are details of the above five considerations.
The first note: objects that need to be locked are visible in multiple threads and are the same object. "Visible" It is obvious that if an object is not visible, it cannot be locked. "Same Object", it is also easy to understand, if the lock is not the same object, then how to synchronize two objects? It's easy to understand, but it's not necessarily that we don't make mistakes on this. To help you understand this recommendation, let's first simulate a scenario where a lock must be used: While traversing a collection, deleting an item in the collection at the same time in another thread. In the following example, if there is no lock statement, an exception InvalidOperationException will be thrown: "The collection has been modified; The enumeration may not be performed":
Public Partial classFormmain:form { PublicFormMain () {InitializeComponent (); } AutoResetEvent AutoSet=NewAutoResetEvent (false); List<string> templist =Newlist<string> () {"init0","init1","Init2" }; Private voidButtonstartthreads_click (Objectsender, EventArgs e) { ObjectSyncobj =New Object(); Thread T1=NewThread (() = { //Be sure to wait for T2 to start before running the following codeAutoset.waitone (); Lock(syncobj) {foreach(varIteminchtemplist) {Thread.Sleep ( +); } } }); T1. IsBackground=true; T1. Start (); Thread T2=NewThread (() = { //notifies T1 that code can be executedAutoset.set (); //sleeps 1 seconds to ensure that the delete operation is in the T1 iterationThread.Sleep ( +); Lock(syncobj) {templist.removeat (1); } }); T2. IsBackground=true; T2. Start (); } }
This is a WinForm form application that needs to demonstrate the functionality of the button in the Click event. Object Syncobj must be the same object in the CLR for thread T1 and t2. So, the above example runs with no problem.
Now, we'll refactor this example. Move the actual work code to a type SampleClass, which operates a static field between multiple SampleClass instances, as follows:
Private voidButtonstartthreads_click (Objectsender, EventArgs e) {SampleClass Sample1=NewSampleClass (); SampleClass Sample2=NewSampleClass (); Sample1. StartT1 (); Sample2. StartT2 (); } classSampleClass { Public Staticlist<string> templist =Newlist<string> () {"init0", "init1","Init2" }; StaticAutoResetEvent AutoSet =NewAutoResetEvent (false); ObjectSyncobj =New Object(); Public voidStartT1 () {Thread T1=NewThread (() = { //Be sure to wait for T2 to start before running the following codeAutoset.waitone (); Lock(syncobj) {foreach(varIteminchtemplist) {Thread.Sleep ( +); } } }); T1. IsBackground=true; T1. Start (); } Public voidStartT2 () {Thread t2=NewThread (() = { //notifies T1 that code can be executedAutoset.set (); //sleeps 1 seconds to ensure that the delete operation is in the T1 iterationThread.Sleep ( +); Lock(syncobj) {templist.removeat (1); } }); T2. IsBackground=true; T2. Start (); } }
The example runs and throws an exception InvalidOperationException:
"The collection has been modified; The enumeration may not be executed. ”
View the methods of type SampleClass StartT1 and StartT2, which are locked internally by the SampleClass instance variable syncobject. An instance variable means that every instance that creates a SampleClass generates a SyncObject object. In this example, the caller creates a total of two sampleclass instances, which are then called separately:
Sample1. StartT1 ();
Sample2. StartT2 ();
That is, the code above locks two different syncobject, which is equivalent to not reaching two threads to lock the same object at all. To fix the above error, just change the syncobject to static.
Also, consider lock (this), and we also don't recommend writing such code in code. If an instance of two objects executes a locked code, the actual lock will be two objects, which is completely unable to achieve the purpose of synchronization.
The second note: In a non-static method, a static variable should not be used as a synchronization object. Some readers may ask, as mentioned earlier, to fix the example problem in the first note, you need to turn SyncObject into static. This seems to be in contradiction with this note. In fact, the sample code in the first note is for illustrative purposes only, and in practical applications we strongly recommend that you do not write such code. When writing multithreaded code, follow this principle:
Static methods of type should be thread-safe and non-static methods need not be thread-safe.
Most of the classes in the FCL follow this principle. As in the previous example, if SyncObject were to be static, it would be equivalent to having a non-static method with thread safety, and the problem was that if there were multiple instances of that type in the application, they would have synchronization when they encountered the lock, which might not be what the developer would like to see. The second consideration can actually be summed up in the first note.
Third Note: A value type Object cannot be a synchronization object. A value type creates a copy when passed to another thread, which is equivalent to two objects locked by each thread. Therefore, a value type object cannot be a synchronization object.
The fourth note: Locking a string is completely unnecessary and quite dangerous. This whole process looks exactly the opposite of the value type. The string is staged into memory in the CLR, and if there are two variables that are assigned the same content string, the two references are pointed to the same piece of memory. So, if you have two places that use lock ("ABC") at the same time, then they actually lock the same object, which causes the entire application to be blocked.
Fifth note: Reduce the visibility of the synchronization object. The most visible range of synchronization objects is typeof (SampleClass). The result returned by the TypeOf method, that is, the type of the types, is common to all instances of SampleClass, that is, the type of all instances points to the result of the TypeOf method. This way, if we lock (typeof (SampleClass)), the instance threads of all sampleclass in the current application will be all synchronized. This encoding is completely unnecessary, and such synchronization objects are too open.
In general, a synchronization object should not be a public variable or property. In earlier versions of the FCL, some common collection types (such as ArrayList) provided public property syncroot, which allowed us to lock in for some thread-safe operation. So you will feel that we have just concluded that it is not correct. In fact, most of the ArrayList operations do not involve multi-threaded synchronization, so its approach is more of a single-threaded application scenario. Thread synchronization is a very time-consuming (inefficient) operation. If all non-static methods of ArrayList are considered thread-safe, then ArrayList can completely turn this syncroot into a static private one. Now it turns the syncroot into public, letting the caller decide for themselves whether the operation requires thread safety. When we write code, unless there is such a requirement, we should always consider reducing the visibility of synchronization objects, to hide the synchronization objects, only open to themselves or their own subclasses is enough (there is not much to open to subclasses).
Turn from: 157 recommendations for writing high-quality code to improve C # programs Minjia
157 recommendations for writing high-quality code to improve C # programs--Recommendation 73: Avoid locking inappropriate synchronization objects