Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explanation of Text on Threading in "C# 3.0 in a Nutshell"

While reading C# 3.0 in a Nutshell by Joseph and Ben Albahari, I came across the following paragraph (page 673, first paragraph in section titled "Signaling with Wait and Pulse")

"The Monitor class provides another signalling construct via two static methods, Wait and Pulse. The principle is that you write the signalling logic yourself using custom flags and fields (enclosed in lock statements), and then introduce Wait and Pulse commands to mitigate CPU spinning. The advantage of this low-level approach is that with just Wait, Pulse, and the lock statement, you can achieve the functionality of AutoResetEvent, ManualResetEvent, and Semaphore, as well as WaitHandle's static methods WaitAll and WaitAny. Furthermore, Wait and Pulse can be amenable in situations where all of the wait handles are parsimoniously challenged."

My question is, what is the correct interpretation of the last sentence?

  • A situation with a decent/large number of wait handles where WaitOne() is only occasionally called on any particular wait handle.
  • A situation with a decent/large number of wait handles where rarely does more than one thread tend to block on any particular wait handle.
  • Some other interpretation.

Would also appreciate illuminating examples of such situations and perhaps how and/or why they are more efficiently handled via Wait and Pulse rather than by other methods.

Thank you!

Edit: I found the text online here

like image 407
RAL Avatar asked Feb 23 '10 23:02

RAL


1 Answers

What this is saying is that there are some situations where Wait and Pulse provides a simpler solution than wait handles. In general, this happens where:

  • The waiter, rather than the notifier, decides when to unblock
  • The blocking condition involves more than a simple flag (perhaps several variables)

You can still use wait handles in these situations, but Wait/Pulse tends to be simpler. The great thing about Wait/Pulse is that Wait releases the underlying lock while waiting. For instance, in the following example, we're reading _x and _y within the safety of a lock - and yet that lock is released while waiting so that another thread can update those variables:

lock (_locker)
{
  while (_x < 10 && _y < 20) Monitor.Wait (_locker);
}

Another thread can then update _x and _y atomically (by virtue of the lock) and then Pulse to signal the waiter:

lock (_locker)
{
  _x = 20;
  _y = 30;
  Monitor.Pulse (_locker);
} 

The disadvantage of Wait/Pulse is that it's easier to get it wrong and make a mistake (for instance, by updating a variable and forgetting to Pulse). In situations where a program with wait handles is equally simple to a program with Wait/Pulse, I'd recommend going with wait handles for that reason.

In terms of efficiency/resource consumption (which I think you were alluding to), Wait/Pulse is usually faster and lighter (as it has a managed implementation). This is rarely a big deal in practice, though. And on that point, Framework 4.0 includes low-overhead managed versions of ManualResetEvent and Semaphore (ManualResetEventSlim and SemaphoreSlim).

Framework 4.0 also provides many more synchronization options that lessen the need for Wait/Pulse:

  • CountdownEvent
  • Barrier
  • PLINQ / Data Parallelism (AsParallel, Parallel.Invoke, Parallel.For, Parallel.ForEach)
  • Tasks and continuations

All of these are much higher-level than Wait/Pulse and IMO are preferable for writing reliable and maintainable code (assuming they'll solve the task at hand).

like image 75
Joe Albahari Avatar answered Oct 04 '22 06:10

Joe Albahari