-1

According to the MSDN:

The Monitor class consists of static (in C#) or Shared (in Visual Basic) methods that operate on an object that controls access to the critical section. The following information is maintained for each synchronized object:

  • A reference to the thread that currently holds the lock.

  • A reference to a ready queue, which contains the threads that are ready to obtain the lock.

  • A reference to a waiting queue, which contains the threads that are waiting for notification of a change in the state of the locked object.

And in this thread, the 2 queues are causing some subtle issue.

I think the root cause of the issue in above thread is there are 2 queues. If there's only ONE queue, whenever the Monitor.Pulse(), only one thread from that single queue can be scheduled to run. There's no way for more than one threads to be in ready state at the same time. So the issue should never happen.

So why does a Monitor keep 2 queues?

smwikipedia
  • 61,609
  • 92
  • 309
  • 482
  • @HenkHolterman Thanks for the comment. But actually my question is inspired by that one. That thread's solution is correct. – smwikipedia Sep 19 '17 at 08:56

1 Answers1

5

I think you misunderstood that SO post. The problem was not caused by Monitor but by a genuine logic error in that OP's Queue class.

The Remove is a familiar pattern, it should use a while instead of an if:

lock (q)
{
    // if (q.Count == 0)
    while (q.Count == 0)
    {
        Monitor.Wait(q);
    }
    ... // use it, we are now sure that q.Count > 0
}

And then you may want an additional way (CancellationToken) to end the whole process.

A Monitor can have 2 sorts of waiting threads, the implementers chose to use 2 queues. Using 1 queue seems possible but that wouldn't change a thing. It would still allow only 1 thread to run at any time, so your understanding goes wrong at this point somehow.

What is happening is that a queued thread can have one of two states:

  • when a thread calls lock(q) and the lock is already taken, it is queued as Ready
  • when a thread calls Wait(q) it is queued as Waiting and needs a Pulse() to wake
  • When Pulse() is called, 1 Waiting thread is moved to the Ready state. But it will not immediately run, it will have to wait its turn. By definition, Pulse() can only be called when the lock is taken.
  • When the Pulsed thread is reactivated, the situation may have changed (ie, another thread consumed the data element).

The Pulse/Wait mechanism is sort of unreliable, when no thread is Waiting a Pulse goes unnoticed. You can't in general rely on Pulse/Wait for exact bookkeeping. And the contract of Monitor does not include fairness

H H
  • 263,252
  • 30
  • 330
  • 514
  • Thanks for the answer. My question is inspired by that one. That thread's solution is correct. I am just curious if there's any *strong reason* to use 2 queues instead of one. Using 1 queue may be simpler to implement, hopefully. – smwikipedia Sep 19 '17 at 08:57
  • No, read the answer from Eric Lippert. The algorithm is not correct, I just added the if/while varation. – H H Sep 19 '17 at 08:59