Thread.sleep vs Monitor.Wait vs RegisteredWaitHandle?

时间:2012-07-08 08:31:30

标签: c# .net multithreading .net-4.0 clr

(以下项目有不同的目标,但我很有趣,知道他们如何“PAUSEd”)

问题

Thread.sleep - 它是否会影响系统的性能?是否会使线程与其等待相关?

Monitor.Wait怎么样?他们“等待”的方式有什么不同?他们在等待中占用一个线程吗?

RegisteredWaitHandle怎么样?此方法接受在等待时执行的委托 手柄发出信号。在等待的时候,它不会占用一个线程。

所以某些线程暂停并且可以被代表唤醒,而其他线程只是等待?旋转?

有人可以让事情更清楚吗?

修改

http://www.albahari.com/threading/part2.aspx

enter image description here

5 个答案:

答案 0 :(得分:8)

Thread.SleepMonitor.Wait都将帖子放入the WaitSleepJoin state

  

WaitSleepJoin:线程被阻止。这可能是打电话的结果   Thread :: Sleep或Thread :: Join,请求锁定 - 例如,通过   调用Monitor :: Enter或Monitor :: Wait - 或等待线程   同步对象,如ManualResetEvent。

RegisteredWaitHandle是通过致电RegisterWaitForSingleObject并传递WaitHandle获得的。通常,此类的所有后代都使用阻塞机制,因此调用Wait将再次将该线程放入WaitSleepJoin(例如AutoResetEvent)。

这是MSDN的另一个引用:

  

RegisterWaitForSingleObject方法检查当前的状态   指定对象的WaitHandle。如果对象的状态没有信号,   该方法注册一个等待操作。执行等待操作   来自线程池的线程。委托由工人执行   当对象的状态变为信号或超时时的线程   间隔过去了。

因此,池中的一个线程会等待信号

答案 1 :(得分:6)

关于ThreadPool.RegisterWaitForSingleObject会为每次注册(合并或其他)绑定一个帖子。您可以轻松地测试它:在LINQPad中运行以下脚本,该脚本调用该方法20,000次:

static ManualResetEvent _starter = new ManualResetEvent (false);

void Main()
{
    var regs = Enumerable.Range (0, 20000)
        .Select (_ => ThreadPool.RegisterWaitForSingleObject (_starter, Go, "Some Data", -1, true))
        .ToArray();

    Thread.Sleep (5000);
    Console.WriteLine ("Signaling worker...");
    _starter.Set();
    Console.ReadLine();

    foreach (var reg in regs) reg.Unregister (_starter);
}

public static void Go (object data, bool timedOut)
{
    Console.WriteLine ("Started - " + data);
    // Perform task...
}

如果该代码在5秒“等待”期间占用20,000个线程,则无法正常工作。

修改 - 响应:

  

“这是一个证明。但是仍然有一个线程可以检查   仅信号?在线程池?“

这是一个实现细节。是的,可以使用单个线程实现,该线程将回调卸载到托管线程池,尽管不能保证这一点。等待句柄最终由操作系统管理,这很可能也会触发回调。它可能在其内部实现中使用一个线程(或少量线程)。或者使用中断,它可能不会阻止单个线程。它甚至可能根据操作系统版本而有所不同。这是一个与我们无关的实施细节。

答案 2 :(得分:4)

虽然它是真的RegisterWaitForSingleObject创建等待线程,但不是每次调用都会创建一个。

来自MSDN

  

在需要时自动创建新的等待线程

来自Raymond Chen的blog

  

...而不是花费整个线程,它花费更接近(但不完全)1/64的线程

因此,使用RegisterWaitForSingleObject通常比创建自己的等待线程更好。

答案 3 :(得分:3)

ThreadPool.g RegisterWaitForSingleObject最终调用其本机实现 QueueUserAPC。参见转子源(sscli20 \ clr \ src \ vm \ win32threadpool.cpp(1981))。与Wait Thread.Sleep不同,当您使用RegisterWaitForSingleObject时,您的线程不会停止。

而是为此线程注册了具有用户模式回调的FIFO队列,当线程处于可警告状态时将调用该队列。这意味着你可以继续工作,当你的线程被阻止时,操作系统将处理已注册的回调,让你的线程有机会在等待时做一些有意义的事情。

<强> EDIT1:

完成分析。在调用RegisterWaitForSingleObject的线程上,当线程处于可警告状态时,将在线程上调用回调。一旦发生这种情况,调用RegisterWaitForSingleObject的线程将执行CLR回调,该回调会注册另一个回调,该回调由线程池回调等待线程处理,该线程仅在那里等待信号回调。然后,此线程池回调等待线程将定期检查已发出信号的回调。

此等待线程最终调用QueueUserWorkItem,以便在线程池线程上执行信号回调。

答案 4 :(得分:3)

Thread.SleepRegisteredWaitHandle在不同级别工作。让我试着澄清一下:

进程有多个线程,它们同时执行(取决于操作系统调度程序)。如果一个线程调用Thread.SleepMonitor.Wait,它就不会旋转 - 它被置于WaitSleepJoin状态,并且CPU被赋予其他线程。

现在,当您有许多同时工作项时,您使用线程池 - 一种创建多个线程的机制,并使用自己对工作项的理解来调度对其线程的调用。在此模型中,从线程池调度程序调用工作线程以执行某些工作,然后返回池。如果工作线程调用阻塞操作 - 如Thread.SleepMonitor.Wait - 此线程被“绑定”,因为线程池调度程序无法将其用于其他工作项。

我不熟悉实际的API,但我认为RegisteredWaitHandle会告诉线程池调度程序在需要时调用工作线程 - 并且你自己的线程没有“捆绑”,并且可以继续工作或返回线程池。