After a short break, I’m back for more on multithreading. In the last post, we’ve talked a little bit about synchronization. Today we’re going to keep talking about it and we’re going to take a quick look at how we can use kernel objects for synchronizing access to shared resources or to control the execution of a specific code zone. Initially, I though in going straight to the high level synchronization structures, but since they might end up delegating in these kernel objects, I thought that a quick intro wouldn’t hurt,right?
Ok, so lets concentrate on the basics…There are several kernel objects which are exposed through several different APIs. In traditional Win32, each kernel object is represented by a HANDLE (which you typically need to close after you’re done with it). Each kernel object can be in one of two states: signaled and non signaled. Transition between these states depend on the specific type of kernel object we’re using. For instance, an OS thread is represented by a Thread Kernel object (represented in Win32 as a HANDLE) and it transitions from the non signaled state to the signaled state when the thread is terminated (and, as we’ve seen, the recommended way for a thread to terminate is by letting it run all the code to the end).
If we want,we can make a thread wait on any kernel object until it gets signaled. In practice,this means that we can make a thread block until the kernel object is signaled (notice that we can make several threads wait on a single kernel object). When this happens and a thread blocks, there’s a context switch and the thread is removed from the processor. When the kernel object the thread is waiting on transitions to the signaled state, the thread might (or might not) be awaken: it all depends on the type of the kernel object (some will wake only a single thread while others end up waking several threads – we’ll come back to this when we talk about the concrete synchronization objects).
Now is a good time to mention that in this series of posts we’re only interested in waiting on the synchronization kernel objects (ok, we can wait on any kernel object, but typically we’ll be waiting on the synchronization objects): mutexes, sempahores, events (auto and manual) and timers. The .NET framework lets introduces a base class on which we can wait that is extended by mutexes, semaphores and events (there’s no direct support for timers). I’m talking about the the WaitHandle class, which is subclassed by the Mutex, Semaphore, AutoResetEvent and ManualResetEvent classes (these last two extend the EventWaitHandle class).
After getting a reference to a WaitHandle, we can use any of the overloads of the instance WaitOne methods to make the thread wait for the kernel object to get into a signaled state. We can also use the static WaitAll or WaitAny if we need to wait on several kernel objects to become signaled (the first method will block the thread until all the kernel objects passed into it get into the signaled state; the second will unblock when any of the kernel objects passed into transition to the signaled state).
After a thread gets blocked waiting for a kernel object to transition from the non signaled to the signaled state, it might wake up without the corresponding non signaled to signaled transition. This happens when the thread performs a so called “alertable wait” (in unmanaged code, you can control this, but in managed, threads always perform “alertable waits”). An thread that is blocked in an “alertable wait” might be awakened whenever it receives an APC call (which is dispatched automatically). Another interesting example where we might need to awake a blocked thread is when the thread created a window (and notice that you end up doing this whenever you instantiate COM STA component). In this case, we need to pump the messages and that means that we need to “wake” the thread before the kernel object has performed its transition. Failing to do it means that we’ll end up with a non-responding window.
Whenever we wait on a managed WaitHandle, we end up getting an “alertable wait”. This means that CLR will end up pumping messages for you (at least, COM RPC messages). In my opinion, this is a good thing (just think about all those Win 32 methods you could use for waiting on a kernel object – for instance, there were WaitForSingleObjectEx, MsgWaitForSimpleObjectsEx, etc, etc, etc). Interestingly (or not), “alertable waits” end up complicating our code, especially when we need to wait and need to specify a timeout. In managed code, the CLR takes care of everything and adapts the timeout if a thread is awaked before a kernel object transitions to the signaled stated. No such luck for the guys writing unmanaged code (yes, we managed programmers are spoiled!)
And I guess I’ll stop for today. When I started writing this post, I thought about showing a concrete example of the usage of one of the kernel objects, but as you can see, this is already a long post and I’ll defer the code for the next post. btw, if you’re interested in a more deep dive on this stuff, then you must really buy Joe Duffy’s master piece (review here) on writing concurrent apps for windows.
And that’s it for today. Keep tuned for more on multithreaded apps!