(以下项目有不同的目标,但我很有趣知道它们是如何“暂停”的)
问题
Thread.sleep
- 它会影响系统的性能吗?它是否会在等待时占用线程?
怎么样 Monitor.Wait
?他们“等待”的方式有什么不同?他们是否在等待时占用了线程?
怎么样RegisteredWaitHandle
?此方法接受在发出等待句柄信号时执行的委托。在等待时,它不会占用线程。
所以一些线程被暂停并且可以被委托唤醒,而其他线程只是等待?旋转 ?
有人可以让事情更清楚吗?
编辑
(以下项目有不同的目标,但我很有趣知道它们是如何“暂停”的)
问题
Thread.sleep
- 它会影响系统的性能吗?它是否会在等待时占用线程?
怎么样 Monitor.Wait
?他们“等待”的方式有什么不同?他们是否在等待时占用了线程?
怎么样RegisteredWaitHandle
?此方法接受在发出等待句柄信号时执行的委托。在等待时,它不会占用线程。
所以一些线程被暂停并且可以被委托唤醒,而其他线程只是等待?旋转 ?
有人可以让事情更清楚吗?
编辑
两者都Thread.Sleep
将Monitor.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 个线程,它就不可能工作。
编辑- 回应:
“这是一个证明。但是否还有一个线程只检查信号?在线程池中?”
这是一个实现细节。是的,它可以通过将回调卸载到托管线程池的单个线程来实现,尽管不能保证这一点。等待句柄最终由操作系统管理,这很可能也会触发回调。它可能在其内部实现中使用一个线程(或少量线程)。或者使用中断,它可能不会阻塞单个线程。它甚至可能因操作系统版本而异。这是一个与我们没有真正关系的实现细节。
ThreadPool.g RegisterWaitForSingleObject 最终会在其本机实现中调用 QueueUserAPC。请参阅转子源 (sscli20\clr\src\vm\win32threadpool.cpp(1981))。与 Wait Thread.Sleep 不同,当您使用 RegisterWaitForSingleObject 时,您的线程不会停止。
而是为这个线程注册了一个带有用户模式回调的 FIFO 队列,当线程处于警报状态时将调用该队列。这意味着您可以继续工作,当您的线程被阻塞时,操作系统将处理注册的回调,让您的线程有机会在等待时做一些有意义的事情。
编辑1:
完成分析。在调用 RegisterWaitForSingleObject 的线程上,当线程处于警报状态时,会在线程上调用回调。一旦发生这种情况,调用 RegisterWaitForSingleObject 的线程将执行一个 CLR 回调,该回调会注册另一个由线程池回调等待线程处理的回调,该线程池回调等待线程仅用于等待信号回调。然后,此线程池回调等待线程将定期检查信号回调。
这个等待线程最终会调用 QueueUserWorkItem 以在线程池线程上执行信号回调。
Thread.Sleep
并RegisteredWaitHandle
在不同层次上工作。让我尝试清除它:
进程有多个线程,它们同时执行(取决于操作系统调度程序)。如果一个线程调用Thread.Sleep
or Monitor.Wait
,它不会旋转 - 它被置于 WaitSleepJoin 状态,并将 CPU 分配给其他线程。
现在,当您有许多同时工作的项目时,您可以使用线程池——一种创建多个线程的机制,并使用它自己对工作项目的理解将调用分派给它的线程。在此模型中,工作线程从线程池调度程序中调用以完成一些工作,然后返回池中。如果一个工作线程调用一个阻塞操作——比如Thread.Sleep
或Monitor.Wait
——这个线程被“捆绑”了,因为线程池调度程序不能将它用于额外的工作项。
我不熟悉实际的 API,但我认为RegisteredWaitHandle
会告诉线程池调度程序在需要时调用工作线程——并且您自己的线程不会“被捆绑”,并且可以继续其工作或返回线程池。