In the latest posts, we’ve taken a look at several kernel objects which we can use for data and control synchronization. Today we’re going to start looking at the synchronization primitives. These structures should always be used (whenever possible, of course) instead of the kernel objects we’ve met in previous posts. Why? Because they’re cheaper.
Why cheaper? well, the truth is that the kernel objects we’ve looked at in previous posts will always incur in kernel transitions (needed for accessing the internal structures they use). On the other hand, the primitives we’re going to look at in the next couple of posts tend to allocate kernel objects only when they’re needed (ie, they use a lazy allocation algorithm). What this means is that you’ll use them in user mode and you’ll only incur into kernel transitions when you really have to wait for something. Here are the primitives we’ll be looking at:
- locks: in .NET, you’ll acquire a lock by using the Monitor.Enter method and you leave one by invoking the Monitor.Leave method. There’s also a TryEnter method which you can use to try to acquire the lock;
- reader/writer locks: allow several threads to read while only allowing one the write. In .NET there are two classes you can use whenever you need this kind of locks: ReaderWriterLock and ReaderWriterLockSlim (the last is the preferred option);
- condition variables: in .NET, you have access to these type of primitive through the Wait, Pulse and PulseAll methods of the Monitor class. Condition variables allow you to have one or more threads waiting for an occurrence of a specific event.
On the next posts we’ll take a deep dive into each of these options. Keep tuned for more!