11

(以下项目有不同的目标,但我很有趣知道它们是如何“暂停”的)

问题

Thread.sleep- 它会影响系统的性能吗?它是否会在等待时占用线程?

怎么样 Monitor.Wait ?他们“等待”的方式有什么不同?他们是否在等待时占用了线程?

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

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

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

编辑

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

在此处输入图像描述

4

5 回答 5

8

两者都Thread.SleepMonitor.Wait线程置于WaitSleepJoin状态

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

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

这是 MSDN 的另一句话:

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

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

于 2012-07-08T08:41:50.360 回答
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 个线程,它就不可能工作。

编辑- 回应:

“这是一个证明。但是否还有一个线程只检查信号?在线程池中?”

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

于 2012-07-09T01:15:26.477 回答
5

虽然确实RegisterWaitForSingleObject会创建等待线程,但并非每个调用都会创建一个。

来自MSDN

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

来自 Raymond Chen 的博客

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

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

于 2012-07-09T06:46:05.643 回答
3

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

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

编辑1:

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

这个等待线程最终会调用 QueueUserWorkItem 以在线程池线程上执行信号回调。

于 2012-07-08T09:27:52.590 回答
3

Thread.SleepRegisteredWaitHandle在不同层次上工作。让我尝试清除它:

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

现在,当您有许多同时工作的项目时,您可以使用线程池——一种创建多个线程的机制,并使用它自己对工作项目的理解将调用分派给它的线程。在此模型中,工作线程从线程池调度程序中调用以完成一些工作,然后返回池中。如果一个工作线程调用一个阻塞操作——比如Thread.SleepMonitor.Wait——这个线程被“捆绑”了,因为线程池调度程序不能将它用于额外的工作项。

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

于 2012-07-08T11:39:26.760 回答