C#:监视器 - 等待、脉冲、脉冲全部

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1559293/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 18:51:07  来源:igfitidea点击:

C# : Monitor - Wait,Pulse,PulseAll

c#multithreading

提问by

I am having hard time in understanding Wait(), Pulse(), PulseAll(). Will all of them avoid deadlock? I would appreciate if you explain how to use them?

我有理解困难时期 Wait()Pulse()PulseAll()。他们都会避免僵局吗?如果您解释如何使用它们,我将不胜感激?

采纳答案by Marc Gravell

Short version:

精简版:

lock(obj) {...}

is short-hand for Monitor.Enter/ Monitor.Exit(with exception handling etc). If nobody else has the lock, you can get it (and run your code) - otherwise your thread is blocked until the lock is aquired (by another thread releasing it).

Monitor.Enter/ 的简写Monitor.Exit(带有异常处理等)。如果没有其他人拥有锁,您可以获得它(并运行您的代码) - 否则您的线程将被阻塞,直到获得锁(由另一个线程释放它)。

Deadlock typically happens when either A: two threads lock things in different orders:

死锁通常发生在 A: 两个线程以不同的顺序锁定事物时:

thread 1: lock(objA) { lock (objB) { ... } }
thread 2: lock(objB) { lock (objA) { ... } }

(here, if they each acquire the first lock, neither can everget the second, since neither thread can exit to release their lock)

(在这里,如果他们每获得第一锁,也不能永远获得第二,因为没有线程可以退出来释放自己的锁)

This scenario can be minimised by always locking in the same order; and you can recover (to a degree) by using Monitor.TryEnter(instead of Monitor.Enter/lock) and specifying a timeout.

这种情况可以通过始终以相同的顺序锁定来最小化;并且您可以通过使用Monitor.TryEnter(而不是Monitor.Enter/ lock)并指定超时来恢复(在一定程度上)。

or B: you can block yourself with things like winforms when thread-switching while holding a lock:

或 B:在持有锁的情况下进行线程切换时,您可以使用诸如 winforms 之类的东西来阻止自己:

lock(obj) { // on worker
    this.Invoke((MethodInvoker) delegate { // switch to UI
        lock(obj) { // oopsiee!
            ...
        }
    });
}

The deadlock appears obvious above, but it isn't so obvious when you have spaghetti code; possible answers: don't thread-switch while holding locks, or use BeginInvokeso that you can at least exit the lock (letting the UI play).

死锁在上面看起来很明显,但是当你有意大利面条式代码时就不那么明显了;可能的答案:不要在持有锁时进行线程切换,或者使用BeginInvoke以便您至少可以退出锁(让 UI 播放)。



Wait/Pulse/PulseAllare different; they are for signalling. I use this in this answerto signal so that:

Wait/ Pulse/PulseAll是不同的; 它们是用来发信号的。我在这个答案中使用它来发出信号,以便:

  • Dequeue: if you try to dequeue data when the queue is empty, it waits for another thread to add data, which wakes up the blocked thread
  • Enqueue: if you try and enqueue data when the queue is full, it waits for another thread to remove data, which wakes up the blocked thread
  • Dequeue: 如果在队列为空时尝试出列数据,它会等待另一个线程添加数据,从而唤醒被阻塞的线程
  • Enqueue: 如果在队列已满时尝试将数据入队,它会等待另一个线程删除数据,从而唤醒被阻塞的线程

Pulseonly wakes up onethread - but I'm not brainy enough to prove that the next thread is always the one I want, so I tend to use PulseAll, and simply re-verify the conditions before continuing; as an example:

Pulse只唤醒一个线程 - 但我不够聪明,无法证明下一个线程始终是我想要的线程,所以我倾向于使用PulseAll,并在继续之前简单地重新验证条件;举个例子:

        while (queue.Count >= maxSize)
        {
            Monitor.Wait(queue);
        }

With this approach, I can safely add other meanings of Pulse, without my existing code assuming that "I woke up, therefore there is data" - which is handy when (in the same example) I later needed to add a Close()method.

使用这种方法,我可以安全地添加 的其他含义Pulse,而无需我现有的代码假设“我醒来,因此有数据” - 这在(在同一示例中)我稍后需要添加Close()方法时很方便。

回答by Vitaliy Liptchinsky

No, they don't protect you from deadlocks. They are just more flexible tools for thread synchronization. Here is a very good explanation how to use them and very important pattern of usage - without this pattern you will break all the things: http://www.albahari.com/threading/part4.aspx

不,它们不能保护您免于陷入僵局。它们只是用于线程同步的更灵活的工具。这是一个很好的解释如何使用它们和非常重要的使用模式 - 如果没有这种模式,你将破坏所有的东西:http: //www.albahari.com/threading/part4.aspx

回答by Brian Rasmussen

They are tools for synchronizing and signaling between threads. As such they do nothing to prevent deadlocks, but if used correctly they can be used to synchronize and communicate between threads.

它们是用于在线程之间同步和发出信号的工具。因此,它们对防止死锁没有任何作用,但如果使用得当,它们可用于线程之间的同步和通信。

Unfortunately most of the work needed to write correct multithreaded code is currently the developers' responsibility in C# (and many other languages). Take a look at how F#, Haskell and Clojure handles this for an entirely different approach.

不幸的是,编写正确的多线程代码所需的大部分工作目前是 C#(和许多其他语言)中的开发人员的责任。看看 F#、Haskell 和 Clojure 如何以完全不同的方法处理这个问题。

回答by gatopeich

Simple recipe for use of Monitor.Wait and Monitor.Pulse. It consists of a worker, a boss, and a phone they use to communicate:

使用 Monitor.Wait 和 Monitor.Pulse 的简单方法。它由一个工人、一个老板和一个他们用来交流的电话组成:

object phone = new object();

A "Worker" thread:

一个“工人”线程:

lock(phone) // Sort of "Turn the phone on while at work"
{
    while(true)
    {
        Monitor.Wait(phone); // Wait for a signal from the boss
        DoWork();
        Monitor.PulseAll(phone); // Signal boss we are done
    }
}

A "Boss" thread:

一个“老板”线程:

PrepareWork();
lock(phone) // Grab the phone when I have something ready for the worker
{
    Monitor.PulseAll(phone); // Signal worker there is work to do
    Monitor.Wait(phone); // Wait for the work to be done
}

More complex examples follow...

更复杂的例子如下...

A "Worker with something else to do":

一个“有其他事情要做的工人”:

lock(phone)
{
    while(true)
    {
        if(Monitor.Wait(phone,1000)) // Wait for one second at most
        {
            DoWork();
            Monitor.PulseAll(phone); // Signal boss we are done
        }
        else
            DoSomethingElse();
    }
}

An "Impatient Boss":

一个“不耐烦的老板”:

PrepareWork();
lock(phone)
{
    Monitor.PulseAll(phone); // Signal worker there is work to do
    if(Monitor.Wait(phone,1000)) // Wait for one second at most
        Console.Writeline("Good work!");
}

回答by Tim Lovell-Smith

Unfortunately, noneof Wait(), Pulse() or PulseAll() have the magical property which you are wishing for - which is that by using thisAPI you will automatically avoid deadlock.

不幸的是,没有等待(),脉冲()或PulseAll()有你所希望的神奇特性-这是通过使用这个API,你会自动避免死锁。

Consider the following code

考虑以下代码

object incomingMessages = new object(); //signal object

LoopOnMessages()
{
    lock(incomingMessages)
    {
        Monitor.Wait(incomingMessages);
    }
    if (canGrabMessage()) handleMessage();
    // loop
}

ReceiveMessagesAndSignalWaiters()
{
    awaitMessages();
    copyMessagesToReadyArea();
    lock(incomingMessages) {
        Monitor.PulseAll(incomingMessages); //or Monitor.Pulse
    }
    awaitReadyAreaHasFreeSpace();
}

This code will deadlock! Maybe not today, maybe not tomorrow. Most likely when your code is placed under stress because suddenly it has become popular or important, and you are being called to fix an urgent issue.

这段代码会死锁!也许不是今天,也许不是明天。最有可能的情况是,您的代码因为突然变得流行或重要而承受压力,而您被要求解决紧急问题。

Why?

为什么?

Eventually the following will happen:

最终会发生以下情况:

  1. All consumer threads are doing some work
  2. Messages arrive, the ready area can't hold any more messages, and PulseAll() is called.
  3. No consumer gets woken up, because none are waiting
  4. All consumer threads call Wait() [DEADLOCK]
  1. 所有消费者线程都在做一些工作
  2. 消息到达,就绪区域不能再容纳任何消息,调用 PulseAll()。
  3. 没有消费者被唤醒,因为没有人在等待
  4. 所有消费者线程都调用 Wait() [DEADLOCK]

This particular example assumes that producer thread is never going to call PulseAll() again because it has no more space to put messages in. But there are many, manybroken variations on this code possible. People will try to make it more robust by changing a line such as making Monitor.Wait();into

这个特定的例子假设生产者线程永远不会再次调用 PulseAll(),因为它没有更多的空间来放置消息。但是这段代码可能有很多很多损坏的变体。人们会尝试通过改变一条线来使它更健壮,比如制作Monitor.Wait();

if (!canGrabMessage()) Monitor.Wait(incomingMessages);

Unfortunately, that still isn't enough to fix it. To fix it you alsoneed to change the locking scope where Monitor.PulseAll()is called:

不幸的是,这仍然不足以修复它。要修复它,您需要更改Monitor.PulseAll()调用的锁定范围:

LoopOnMessages()
{
    lock(incomingMessages)
    {
        if (!canGrabMessage()) Monitor.Wait(incomingMessages);
    }
    if (canGrabMessage()) handleMessage();
    // loop
}

ReceiveMessagesAndSignalWaiters()
{
    awaitMessagesArrive();
    lock(incomingMessages)
    {
        copyMessagesToReadyArea();
        Monitor.PulseAll(incomingMessages); //or Monitor.Pulse
    }
    awaitReadyAreaHasFreeSpace();
}

The key point is that in the fixed code, the locks restrict the possible sequences of events:

关键是在固定代码中,锁限制了可能的事件序列:

  1. A consumer threads does its work and loops

  2. That thread acquires the lock

    And thanks to locking it is now true that either:

  3. a. Messages haven't yetarrived in the ready area, and it releases the lock by calling Wait() BEFORE the message receiver thread can acquire the lock and copy more messages into the ready area, or

    b. Messages have already arrivedin the ready area and it receives the messages INSTEAD OF calling Wait(). (And while it is making this decision it is impossible for the message receiver thread to e.g. acquire the lock and copy more messages into the ready area.)

  1. 消费者线程完成其工作并循环

  2. 该线程获取锁

    而得益于锁定现在是真的,要么

  3. 一种。消息尚未到达就绪区,在消息接收者线程获取锁并将更多消息复制到就绪区之前,它通过调用 Wait() 释放锁,

    湾 消息已经到达就绪区域,它接收消息 INSTEAD OF 调用 Wait()。(并且当它做出这个决定时,消息接收者线程不可能例如获取锁并将更多消息复制到就绪区域中。)

As a result the problem of the original code now never occurs: 3. When PulseEvent() is called No consumer gets woken up, because none are waiting

结果,原来代码的问题现在从来没有出现过: 3. 当 PulseEvent() 被调用时,没有消费者被唤醒,因为没有消费者在等待

Now observe that in this code you have to get the locking scope exactly right. (If, indeed I got it right!)

现在请注意,在这段代码中,您必须完全正确地获得锁定范围。(如果,我确实做对了!)

And also, since you must use the lock(or Monitor.Enter()etc.) in order to use Monitor.PulseAll()or Monitor.Wait()in a deadlock-free fashion, you still have to worry about possibility of otherdeadlocks which happen because of that locking.

而且,由于您必须使用lock(或Monitor.Enter()等)才能使用Monitor.PulseAll()Monitor.Wait()以无死锁的方式使用,因此您仍然必须担心由于该锁定而发生的其他死锁的可能性。

Bottom line: these APIs are also easy to screw up and deadlock with, i.e. quite dangerous

底线:这些 API 也很容易搞砸和陷入僵局,即非常危险

回答by jdpilgrim

Something that total threw me here is that Pulsejust gives a "heads up" to a thread in a Wait. The Waiting thread will not continue until the thread that did the Pulsegives up the lockand the waiting thread successfully wins it.

总把我扔到这里的是,它Pulse只是对Wait. 等待线程将不会继续,直到执行此操作的线程Pulse放弃锁并且等待线程成功赢得

lock(phone) // Grab the phone
{
    Monitor.PulseAll(phone); // Signal worker
    Monitor.Wait(phone); // ****** The lock on phone has been given up! ******
}

or

或者

lock(phone) // Grab the phone when I have something ready for the worker
{
    Monitor.PulseAll(phone); // Signal worker there is work to do
    DoMoreWork();
} // ****** The lock on phone has been given up! ******

In both cases it's not until "the lock on phone has been given up" that another thread can get it.

在这两种情况下,直到“手机锁定已被放弃”,另一个线程才能获得它。

There might be other threads waiting for that lock from Monitor.Wait(phone)or lock(phone). Only the one that wins the lock will get to continue.

可能还有其他线程在等待来自Monitor.Wait(phone)或 的锁lock(phone)。只有赢得锁定的人才能继续。

回答by Wolfie

This is a simple example of monitor use :

这是监视器使用的一个简单示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp4
{
    class Program
    {
        public static int[] X = new int[30];
        static readonly object _object = new object();
        public static int count=0;
        public static void PutNumbers(int numbersS, int numbersE)
        {

            for (int i = numbersS; i < numbersE; i++)
            {
                Monitor.Enter(_object);
                try
                {
                    if(count<30)
                    {
                        X[count] = i;
                        count++;
                        Console.WriteLine("Punt in " + count + "nd: "+i);
                        Monitor.Pulse(_object); 
                    }
                    else
                    {
                        Monitor.Wait(_object);
                    }
                }
                finally
                {
                    Monitor.Exit(_object);
                }
            }
        }

        public static void RemoveNumbers(int numbersS)
        {

            for (int i = 0; i < numbersS; i++)
            {
                Monitor.Enter(_object);
                try
                {
                    if (count > 0)
                    {
                        X[count] = 0;
                        int x = count;
                        count--;
                        Console.WriteLine("Removed " + x + " element");
                        Monitor.Pulse(_object);

                    }
                    else
                    {
                        Monitor.Wait(_object);
                    }
                }
                finally
                {
                    Monitor.Exit(_object);
                }
            }
        }



        static void Main(string[] args)
        {
            Thread W1 = new Thread(() => PutNumbers(10,50));
            Thread W2 = new Thread(() => PutNumbers(1, 10));
            Thread R1 = new Thread(() => RemoveNumbers(30));
            Thread R2 = new Thread(() => RemoveNumbers(20));
            W1.Start();
            R1.Start();
            W2.Start();
            R2.Start();
            W1.Join();
            R1.Join();
            W2.Join();
            R2.Join();
        }
    }
}