Thread.sleep vs Monitor.Wait vs RegisteredWaitHandle?

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

问题

Thread.sleep – 它是否会影响系统的性能?是否会将线程与其等待捆绑在一起?

那么Monitor.Wait呢? 他们“等待”的方式有什么不同? 他们在等待时会占用一个线程吗?

那么RegisteredWaitHandle呢? 此方法接受在发出等待句柄信号时执行的委托。 虽然它在等待, 但它并没有占用一个线程。

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

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

编辑

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

在此处输入图像描述

Thread.SleepMonitor.Wait都将线程置于WaitSleepJoin状态 :

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

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

这是MSDN的另一个引用:

RegisterWaitForSingleObject方法检查指定对象的WaitHandle的当前状态。 如果对象的状态是无信号的,则该方法注册等待操作。 等待操作由线程池中的线程执行。 当对象的状态发出信号或超时间隔过去时,委托由工作线程执行。

所以池中的线程确实等待信号。

关于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个线程,则无法工作。

编辑 – 响应:

“这是一个证据。但是仍然只有一个线程只在线程池中检查信号吗?”

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

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

来自MSDN :

必要时会自动创建新的等待线程

来自Raymond Chen的博客 :

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

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

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

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

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

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

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

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

EDIT1:

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

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