As I stated in my last post, for the past two days I’ve been sitting in Jeffrey Richter’s threading class. The class is near the end and I can’t say that a lot of new concepts have been taught. Another student and I have decided that the class should have been renamed, “Threading Basics”. That’s not to say anything of Richter’s teaching skill or the content of the class. It just goes to show that if the architecture of a product is right, the threading code should be extremely simple to use.
However, what I love about this class is that it reminds me how much I enjoy this topic. The theory of the perfect architecture doesn’t exist. Additionally, many times the developer has little-to-no ability to push back on a bad architecture. It’s in these cases that you must use your bag of concurrency tricks to work out of the whole the architect(s) put you in. That sound easy enough, but what if the architecture included – *gasp* – “less-than-optimal” decisions in the actual .NET framework classes? What if those decisions were made in the very methods that are supposed to help you with these synchronization problems? These problems perplexed me when I first encountered them and I never really thought to blog about them (I was actually just scared I was doing something wrong). Taking this class gave me the perfect excuse to bring the topic up.
Take a look at the following pattern in C++:
class CConcurrencySample { public: CConcurrencySample( ) { // Initialize a critical section on class construction InitializeCriticalSection( &m_criticalSection ); }; ~CConcurrencySample( ) { // Destroy the critical section on class destruction DeleteCriticalSection( &m_criticalSection ); }; // This method is over-simplified on purpose //////////////////////////////////////////////////////////// BOOL SafeMethod( ) { // Claim ownership of the critical section if( ! TryEnterCriticalSection( &m_criticalSection ) ) { return FALSE; } //////////////////////////////////////////////// // Thread-safe code goes here //////////////////////////////////////////////// // Release ownership of the critical section LeaveCriticalSection( &m_criticalSection ); return TRUE; }; private: CRITICAL_SECTION m_criticalSection; };
Anyone that’s done any work in threading recognizes this pattern. We’ve declared a class named CConcurrencySample. When any code instantiates an instance of CConcurrencySample, the constructor initializes the private CRITICAL_SECTION instance in the object. When any code destroys an instance of CConcurrencySample, the CRITICAL_SECTION is also deleted. With this pattern, we know that all members have access to the critical section and can claim ownership of the critical section at any point. For my purposes, I’ve created a method named SafeMethod that will take the critical section lock when it is called, and release the lock when it leaves the method. The good part about this pattern is that each instance has a way to lock SafeMethod. The downside is that if client doesn’t need to call SafeMethod, then the CRITICAL_SECTION is created, initialized and destroyed without need — uselessly taking up memory (24 bytes on a 32bit OS) and processor cycles.
The CLR implemented this pattern and even expanded on it. They also tried to fix the waste incurred with non-use of the critical section. The CLR implementation works as follows. Each System.Object created by the CLR contains a sync block index (4 bytes on a 32bit OS) that is defaulted to -1. If a critical section is entered within the object, the CLR adds the object’s sync block to an array and sets the object’s sync block index to the position in the array. With me so far? So how does one enter a critical section in .NET? Using the Monitor class, of course. The following example emulates the same functionality as the previous C++ sample by using C#.
public class CConcurrencySample { bool SafeMethod( ) { // Claim ownership of the critical section if( Monitor.TryEnter( this ) ) { return false; } //////////////////////////////////////////////// // Thread-safe code goes here //////////////////////////////////////////////// // Release ownership of the critical section Monitor.Exit( this ); return true; } }
Monitor.Enter and Monitor.Exit are supposed to provide similar functionality of entering and leaving a critical section (respectively). Since all System.Object’s have their own SyncBlock, we just need to pass our current object to the Monitor.Enter and Monitor.Exit method. This performs the lock I described earlier by setting the sync block index. Sounds great, but what’s the difference between that C++ example and the C# pattern? What issue can you see in the .NET framework implementation of this pattern that you don’t see in the C++ sample I provided?
Give up?
The answer simple. In the C++ sample, the lock object (the CRITICAL_SECTION field) is private. This means that no external clients can lock my object. My object controls the locks. In the .NET implementation, Monitor.Enter can take ANY object. ANY caller can lock on ANY object. External clients locking on your object’s SyncBlock can cause a deadlock.
public class CConcurrencySample2 { private Object m_lock = null; bool SafeMethod ( ) { if ( m_lock == null ) { m_lock = new Object( ); } if ( ! Monitor.TryEnter( m_lock ) ) { return false; } //////////////////////////////////////////////// // Thread-safe code goes here //////////////////////////////////////////////// Monitor.Exit( m_lock ); return true; } }
With this approach, we are declaring an plain, privately-declared object in the class. Be careful when using this approach that you don’t use a value-type for your private lock. Value type’s passed to a Monitor will be boxed each time the methods on Monitor is called and a new SyncBlock is used — effectively making the lock useless.
So what does this have to do with IIS? Nothing just yet. But I plan to cover some asynchronous web execution patterns in future posts and I figured this would be a great place to start.
This information is pretty old but sitting in class I realized it might not be completely obvious to everyone just yet. If you were someone in-the-know about this information since WAY back in 2002, please forgive the repitition.