When creating multithreading application, thread-safe access through accessor functions to an internal member variable is very important. Poorly written code may cause the application behave improperly, deadlock, etc. This article shows an example of on how to create a thread-safe access class. To achieve it, one way is the use the lock keyword to make a block of code as critical section. This keyword accepts a parameter of either the type object for the class (such as typeof(MyClass)) or a class instance object (new MyClass()). It uses this type or object to control what you are locking.
The following NoSafeMemberAccess class shows three methods: ReadNumericField, IncrementNumericField and ModifyNumericField. While all of these methods access the internal numericField member, the access is currently not safe for multithreaded access.
public sealed class NoSafeMemberAccess { private NoSafeMemberAccess ( ) {} private static int numericField = 1; public static void IncrementNumericField() { ++numericField; } public static void ModifyNumericField(int newValue) { numericField = newValue; } public static int ReadNumericField() { return (numericField); } }
NoSafeMemberAccess could be used in a multithreaded application, and, therefore, it must be made thread-safe. Consider what would occur if multiple threads were calling the IncrementNumericField method at the same time. It is possible that two calls could occur to IncrementNumericField while the numericField is updated only once. In order to protect against this, we will modify this class by creating an object that we can lock against in critical sections:
public sealed class SaferMemberAccess { private SaferMemberAccess ( ) {} private static int numericField = 1; private static object syncObj = new object(); public static void IncrementNumericField() { lock(syncObj) { ++numericField; } } public static void ModifyNumericField(int newValue) { numericField = newValue; } public static int ReadNumericField() { int readValue = 0; readValue = numericField; return (readValue); } }
Using the lock statement on the syncObj object lets us synchronize access to the numericField member. This now makes this method safe for multithreaded access.
There is a problem with synchronization using an object like syncObj in the SaferMemberAccess example. If you lock an object or type that can be accessed by other objects within the application, other objects may also attempt to lock this same object. This will manifest itself in poorly written code that locks itself, such as the following code:
public class DeadLock { public void Method1() { lock(this) { // Do something } } }
When Method1 is called, it locks the current DeadLock object. Unfortunately, any object that has access to the DeadLock class may also lock it. This is shown here:
using System; using System.Threading; public class AnotherCls { public void DoSomething() { DeadLock deadLock = new DeadLock(); lock(deadLock) { Thread thread = new Thread(new ThreadStart(deadLock.Method1)); thread.Start(); // Do some time consuming task here } } }
The DoSomething method obtains a lock on the deadLock object and then attempts to call the Method1 method of the deadLock object on another thread, after which a very long task is executed. While the long task is executing, the lock on the deadLock object prevents Method1 from being called on the other thread. Only when this long task ends, and execution exits the critical section of the DoSomething method, will the Method1 method be able to acquire a lock on the this object. As you can see, this can become a major headache to track down in a much larger application.
Jeffrey Richter has come up with a relatively simple method to remedy this situation, which he details quite clearly in the article “Safe Thread Synchronization” in the January 2003 issue of MSDN Magazine. His solution is to create a private field within the class to synchronize on. The object itself can only acquire this private field; no outside object or type may acquire it. The DeadLock class can be rewritten, as follows, to fix this problem:
public class DeadLock { private object syncObj = new object(); public void Method1() { lock(syncObj) { // Do something } } }
To clean up your code, you should stop locking any objects or types except for the synchronization objects that are private to your type or object, such as the syncObj in the fixed DeadLock class. This article makes use of this pattern by creating a static syncObj object within the SaferMemberAccess class. The IncrementNumericField, ModifyNumericField, and ReadNumericField methods use this syncObj to synchronize access to the numericField field. Note that if you do not need a lock while the numericField is being read in the ReadNumericField method, you can remove this lock block and simply return the value contained in the numericField field.
Popularity: 31% [?]
RSS feed for comments on this post · TrackBack URI
Leave a reply