Monitor.Pulse & Wait - Unexpected Behaviour

佐手、 提交于 2019-12-07 05:06:23

问题


http://www.codeproject.com/Articles/28785/Thread-synchronization-Wait-and-Pulse-demystified

Queues:

The ready queue is the collection of threads that are waiting for a particular lock. The Monitor.Wait methods introduce another queue: the waiting queue. This is required as waiting for a Pulse is distinct from waiting to acquire a lock. Like the ready queue, the waiting queue is FIFO.

Recommended pattern:

These queues can lead to unexpected behaviour. When a Pulse occurs, the head of the waiting queue is released and is added to the ready queue. However, if there are other threads in the ready queue, they will acquire the lock before the thread that was released. This is a problem, because the thread that acquires the lock can alter the state that the pulsed thread relies on. The solution is to use a while condition inside the lock statement

*Q = Queue.

By that, I understand that when I call Pulse, it does 2 things before it ends. Firstly, it removes one thread from the waiting Q to the ready Q. Secondly, it lets one thread (without knowing who is that thread) in the Ready Q acquire the lock; it doesn't care who acquires the lock (the thread that came from the waiting Q or a thread that was in the ready Q for some reason).

If I'm right about that then why is putting a while before Monitor.Wait helping to fix the problem (problem - the pulse ends even if the thread that came from the waiting Q didn't acquire the lock)?

A. Tell me if I'm right about the purpose of Monitor.Pulse.

B. Why do I need to put a while before Monitor.Wait

The full code of the answer below:

class Program
{
    static Queue<int> queue = new Queue<int>();
    static object someMonitor = new object();

    static void Main(string[] args)
    {
        Thread Thread1 = new Thread(WorkAlltheTime);
        Thread1.Name = "Thread1";
        Thread Thread2 = new Thread(WorkAlltheTime);
        Thread2.Name = "Thread2";
        Thread Thread3 = new Thread(WorkOnce);
        Thread3.Name = "Thread3";
        Thread1.Start();
        Thread2.Start();
        Thread.Sleep(1000);
        Thread3.Start();
        Console.ReadLine();
    }

    static void WorkAlltheTime()
    {
        Console.WriteLine("Came in to Ready Q: " + Thread.CurrentThread.Name);
        lock (someMonitor)
        {
            Console.WriteLine("Came out from Ready Q: " + Thread.CurrentThread.Name);
            // Broken!
            while (queue.Count == 0)
            {
                Console.WriteLine("Came in to Waiting Q: " + Thread.CurrentThread.Name);
                Monitor.Wait(someMonitor);
                Console.WriteLine("Came out from Waiting Q: " + Thread.CurrentThread.Name);
            }
            queue.Dequeue();
            Console.WriteLine("Thread: "+Thread.CurrentThread.Name+" Pulled Out");
        }
    }

    static void WorkOnce()
    {
        lock (someMonitor)
        {
            queue.Enqueue(1);
            Monitor.Pulse(someMonitor);
        }
    }   
}

回答1:


Imagine you're trying to write a producer/consumer queue - you Pulse each time you produce an item, and a consumer needs to wait until there's an item to consume. You'd write code like this:

Foo item;
lock(someMonitor)
{
    while (queue.Count == 0)
    {
        Monitor.Wait(someMonitor);
    }
    item = queue.Dequeue();
}
// Use the item

Suppose you didn't have the while loop, and instead wrote:

Foo item;
lock(someMonitor)
{
    // Broken!
    if (queue.Count == 0)
    {
        Monitor.Wait(someMonitor);
    }
    item = queue.Dequeue();
}
// Use the item

Now suppose you have one thread already waiting, and then another thread just before the lock statement... then a producer pulses the monitor (and adds an item to the queue, of course).

At that point, it's entirely feasible that the thread which hasn't even got to the lock yet will be the first to aquire the lock... at which point by the time the "waiting" thread acquires the lock, the queue would be empty again. With just a single if statement, without looping, you'd end up dequeuing when the queue is empty, which would fail.

With the while loop, you'll wait again until the next item is produced, which is what you really want.



来源:https://stackoverflow.com/questions/12121151/monitor-pulse-wait-unexpected-behaviour

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!