【问题标题】:Monitor.Enter vs Monitor.WaitMonitor.Enter 与 Monitor.Wait
【发布时间】:2018-06-12 17:11:49
【问题描述】:

我仍然不确定这两个调用之间的区别。来自 MSDN,

Monitor.Enter(Object)获取指定对象的排他锁。

Monitor.Wait(Object) 释放对象上的锁并阻塞当前线程,直到它重新获得锁为止。

据此,我假设 Monitor.Wait 与 Monitor.Enter 相同,只是它在重新获取之前先释放对象上的锁定。

当前线程是否必须首先拥有锁?不同的线程如何强制释放对象的锁?为什么同一个线程要重新获取锁?

【问题讨论】:

标签: c# multithreading locking


【解决方案1】:

根据MSDN: Monitor.Wait Method(Object)

SynchronizationLockException:调用线程不拥有指定对象的锁。

换句话说:您只能在已经拥有锁时调用Monitor.Wait(Object),而调用Monitor.Enter(Object)是为了获取锁。

至于为什么需要Monitor.Wait:如果您的线程意识到它缺少继续执行的信息(例如它正在等待信号),您可能希望让其他线程进入临界区,因为并非所有线程具有相同的先决条件。

要让等待的线程继续执行,您需要在释放锁之前调用Monitor.Pulse(Object)Monitor.PulseAll(Object) (否则,您将得到与Monitor.Wait(Object))。

请记住,在一个脉冲之后和释放锁之后获取锁的下一个线程不一定是接收到该脉冲的线程。

另外请记住,收到脉搏并不等于满足您的条件。您可能还需要再等一会儿:

// make sure to synchronize this correctly ;)
while (ConditionNotMet)
{
    Monitor.Wait(mutex);
    if (ConditionNotMet) // We woke up, but our condition is still not met
        Monitor.Pulse(mutex); // Perhaps another waiting thread wants to wake up?
}

【讨论】:

    【解决方案2】:

    考虑这个例子:

    public class EnterExitExample
    {
        private object myLock;
        private bool running;
    
        private void ThreadProc1()
        {
            while (running)
            {
                lock (myLock)
                {
                    // Do stuff here...
                }
                Thread.Yield();
            }
        }
    
        private void ThreadProc2()
        {
            while (running)
            {
                lock (myLock)
                {
                    // Do other stuff here...
                }
                Thread.Yield();
            }
        }
    }
    

    现在你有两个线程,每个线程都在等待锁,然后做自己的事情,然后释放锁。 lock (myLock) 语法只是 Monitor.Enter(myLock)Monitor.Exit(myLock) 的糖。

    现在让我们看一个更复杂的例子,WaitPulse 发挥作用。

    public class PulseWaitExample
    {
        private Queue<object> queue;
        private bool running;
    
        private void ProducerThreadProc()
        {
            while (running)
            {
                object produced = ...; // Do production stuff here.
                lock (queue)
                {
                    queue.Enqueue(produced);
                    Monitor.Pulse(queue);
                }
            }
        }
    
        private void ConsumerThreadProc()
        {
            while (running)
            {
                object toBeConsumed;
                lock (queue)
                {
                    Monitor.Wait(queue);
                    toBeConsumed = queue.Dequeue();
                }
                // Do consuming stuff with toBeConsumed here.
            }
        }
    }
    

    我们这里有什么?

    生产者在他喜欢的时候就生产一个对象。一旦获得,他就获得了队列的锁定,将对象入队,然后调用Pulse

    同时,消费者没有锁,他通过调用Wait离开了它。一旦他在该对象上获得Pulse,他就会重新锁定,并做他消耗的事情。

    所以你在这里得到的是一个直接的线程到线程通知,告诉消费者有什么事情要做。如果你不这样做,你所能做的就是让消费者继续对集合进行轮询,如果还有什么事情要做。使用Wait,可以确定有。

    【讨论】:

    • 来自另一个预期的答案“当您已经拥有锁时,您只能调用 Monitor.Wait(Object)”。 Producer有锁,Consumer可以调用Monitor.Wait(queue)吗?
    • 生产者持有锁的时间很短,这是Queue.EnqueueMonitor.Pulse 调用所花费的时间,后者释放锁。消费者要么完全在锁块之外,这意味着他将刚刚获得锁,调用Wait,由于挥之不去的Pulse而立即停止等待,或者他在Wait调用内,使他重新获得锁和做他的消耗品。
    • 根据Monitor.Pulse() 上的文档,这看起来像是一个等待发生的死锁:“Monitor 类不维持指示 Pulse 方法已被调用的状态。因此,如果您在没有调用 Pulse 时调用线程正在等待,下一个调用 Wait 的线程就像从未调用过 Pulse 一样阻塞。如果两个线程正在使用 Pulse 和 Wait 进行交互,这可能会导致死锁。将此与 AutoResetEvent 类的行为进行对比[... ]"
    • 我继续尝试了这个例子。 ideone.com/ucE2PY - 它没有死锁(生产者不断脉冲),但仍然丢失了一些脉冲。生产者生产的元素总是比消费者消耗的多。每当消费者在其lock 块之外并且生产者发出脉冲时,该脉冲就会丢失。
    【解决方案3】:

    正如 Cristi 所说,幼稚的等待/脉冲代码不起作用。因为您完全错过了这里的关键点:监视器不是消息队列。如果您脉搏并且没有人在等待,则脉搏丢失。 正确的哲学是你在等待一个条件,如果条件不满足,有办法等待它,不吃cpu,不持有锁。在这里,消费者的条件是队列中有东西。 请参阅 https://ideone.com/tWqTS1 哪个有效(来自 Cristi 示例的一个分支)。

    public class PulseWaitExample
    {
        private Queue<object> queue;
        private bool running;
    
        private void ProducerThreadProc()
        {
            while (running)
            {
                object produced = ...; // Do production stuff here.
                lock (queue)
                {
                    queue.Enqueue(produced);
                    Monitor.Pulse(queue);
                }
            }
        }
    
        private void ConsumerThreadProc()
        {
            while (running)
            {
                object toBeConsumed;
                lock (queue)
                {
                    // here is the fix
                    if (queue.Count == 0)
                    { 
                       Monitor.Wait(queue);
                    }
                    toBeConsumed = queue.Dequeue();
                }
                // Do consuming stuff with toBeConsumed here.
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-19
      • 1970-01-01
      • 1970-01-01
      • 2011-09-13
      • 2011-05-23
      • 2012-07-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多