views:

137

answers:

1

I have a simple producer/consumer scenario, where there is only ever a single item being produced/consumed. Also, the producer waits for the worker thread to finish before continuing. I realize that kind of obviates the whole point of multithreading, but please just assume it really needs to be this way (:

This code doesn't compile, but I hope you get the idea:

// m_data is initially null

// This could be called by any number of producer threads simultaneously
void SetData(object foo)
{
    lock(x)                      // Line A
    {
        assert(m_data == null);
        m_data = foo;
        Monitor.Pulse(x)         // Line B
        while(m_data != null)
            Monitor.Wait(x)      // Line C
    }
}

// This is only ever called by a single worker thread
void UseData()
{
    lock(x)                      // Line D
    {
        while(m_data == null)
            Monitor.Wait(x)      // Line E
        // here, do something with m_data
        m_data = null;
        Monitor.Pulse(x)         // Line F
    }
}

Here is the situation that I am not sure about:

Suppose many threads call SetData() with different inputs. Only one of them will get inside the lock, and the rest will be blocked on Line A. Suppose the one that got inside the lock sets m_data and makes its way to Line C.

Question: Could the Wait() on Line C allow another thread at Line A to obtain the lock and overwrite m_data before the worker thread even gets to it?

Supposing that doesn't happen, and the worker thread processes the original m_data, and eventually makes its way to Line F, what happens when that Pulse() goes off?

Will only the thread waiting on Line C be able to get the lock? Or will it be competing with all the other threads waiting on Line A as well?

Essentially, I want to know if Pulse()/Wait() communicate with each other specially "under the hood" or if they are on the same level with lock().

The solution to these problems, if they exist, is obvious of course - just surround SetData() with another lock - say, lock(y). I'm just curious if it's even an issue to begin with.

+3  A: 

There is no guarantee that the consumer will be enqueued on the waiting or ready queue before another producer.
C# and Java style monitors are described on Wikipedia, under "Implicit condition monitors".
A nice overview of what happens in Monitor (taken from this excellent site): alt text

"Could the Wait() on Line C allow another thread at Line A to obtain the lock and overwrite m_data before the worker thread even gets to it?"

Suppose that SetData() is called by two producer threads, P1 & P2.
A consumer thread, C1 is started as well.
P1, P2 and C1 all enter the ready queue.
P1 acquires the lock first.
The waiting queue is empty, Pulse() on line B has no effect.
P1 waits on line C, so it is placed on the waiting queue.
The next thread in the ready queue acquires the lock.
It can equally be P2 or C1 - in the first case, the assertion fails.
You have a race condition.

"Supposing that doesn't happen, and the worker thread processes the original m_data, and eventually makes its way to Line F, what happens when that Pulse() goes off?"

It will move a waiter from the waiting queue to the ready queue.
The lock is held by the thread that issues the Pulse().
The notified thread will get a chance to acquire the lock after the pulsing thread releases the lock (there can already be others in the ready queue).
From MSDN, Monitor.Pulse():
"The thread that currently owns the lock on the specified object invokes this method to signal the next thread in line for the lock. Upon receiving the pulse, the waiting thread is moved to the ready queue. When the thread that invoked Pulse releases the lock, the next thread in the ready queue (which is not necessarily the thread that was pulsed) acquires the lock."

"Will only the thread waiting on Line C be able to get the lock? Or will it be competing with all the other threads waiting on Line A as well?"

All threads that are on the ready queue "compete" for having the lock next.
It does not matter if they got there straight or from the waiting queue by means of Pulse().

The "queues" might be implemented by other means. (Not the queue data structure as such).
This way a Monitor implementation may not guarantee fairness - but may have higher overall throughput/performance.

andras