Thread.sleep与Monitor.Wait与RegisteredWaitHandle有什么区别?

11
以下这些项目的目标不同,但我很感兴趣了解它们如何“PAUSEd”。
问题: Thread.sleep - 它会对系统性能产生影响吗?它是否会占用一个线程进行等待?
那么 Monitor.Wait 呢?它们“等待”的方式有什么区别?它们是否会占用一个线程进行等待?
那么 RegisteredWaitHandle 呢?该方法接受一个委托,当等待处理程序被信号激活时执行。在等待期间,它不会占用线程。
所以一些线程被暂停,并且可以通过委托唤醒,而其他线程只是等待?自旋?
能否有人让事情更清晰明了?

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

enter image description here

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的当前状态。如果对象的状态为未标记,则该方法注册等待操作。等待操作由线程池中的线程执行。当对象的状态变为标记或超时间隔过去时,工作线程将执行委托。

因此,线程池中的线程确实等待信号。

你的意思是一个线程被阻塞了,但不是线程池中的线程? - Royi Namir
线程(非工作线程)和线程(工作线程)之间有什么区别吗? - Royi Namir
@Royi Namir:我认为它们没有任何结构上的区别(即它们都是标准线程),除了它们用于不同的目的。 - Tudor
给我发送一个脉冲 (哈哈) 到 namir.78@gmail.com,我会立即收到并可以开始聊天。 - Royi Namir
1
@Royi Namir:他的回答并没有真正与我的相矛盾。只要我们没有实际实现“ThreadPool”,我们只能根据文档进行推测,这些文档表明池中有一些线程在等待。但我认为这不重要,因为单个等待线程无需担心。重要的是:1.没有浪费工作线程,2.调用“RegisterWaitForSingleObject”的当前线程不会被阻塞。 - Tudor
显示剩余11条评论

6

关于ThreadPool.RegisterWaitForSingleObject,它并不会每次注册都占用一个线程(无论是池化的还是非池化的)。您可以轻松测试:在LINQPad中运行以下脚本,调用该方法20000次:

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个线程,它肯定行不通。
编辑 - 回应:
这是一项实现细节。是的,它可以使用单个线程将回调转移到托管线程池,尽管无法保证这一点。Wait句柄最终由操作系统管理,操作系统很可能也会触发回调。它可能在其内部实现中使用一个线程(或少量线程)。或者使用中断时,它可能不会阻塞单个线程。甚至可能根据操作系统版本而有所不同。这是一项对我们没有实际关联的实现细节。

谢谢回复。这是一个证明。但是在线程池中是否仍有一个仅检查信号的单个线程? - Royi Namir

5
RegisterWaitForSingleObject函数确实可以创建等待线程,但并非每次调用该函数都会创建一个等待线程。
根据MSDN中的说明:

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

根据Raymond Chen的博客中的描述:

...它不会像整个线程那样消耗资源,大约只会消耗(但不完全相同)1/64的线程资源。

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

嗨,(מה קורה?)创建RegisterWaitForSingleObject会创建等待线程,但我们不知道有多少个?所以您认为线程池没有单个线程检查信号状态吗?1/64个线程是什么意思?这是一个分数...请解释一下。 - Royi Namir
正如雷蒙德所解释的那样,它使用了WaitForMultipleObjects函数,因此一个等待线程可用于多个对象(最多64个)。 - Eli Arbel
与线程池相关的内容,每个63个waithandle,它将在waitforany方法中发出等待线程。 - TakeMeAsAGuest

3
ThreadPool.g RegisterWaitForSingleObject在其本地实现中最终调用QueueUserAPC。请参见rotor源代码(sscli20\clr\src\vm\win32threadpool.cpp(1981))。与Wait Thread.Sleep不同,当您使用RegisterWaitForSingleObject时,您的线程不会被停止。
相反,为该线程注册了一个FIFO队列,其中包含用户模式回调,当线程处于可警报状态时将调用该回调。这意味着您可以继续工作,当您的线程被阻塞时,操作系统将处理已注册的回调,使您的线程有机会在等待时执行有意义的事情。 编辑1: 对于调用RegisterWaitForSingleObject的线程,在该线程处于可警报状态时会调用一个回调。一旦发生这种情况,调用RegisterWaitForSingleObject的线程将执行一个CLR回调,该回调会注册另一个回调,由线程池回调等待线程处理,该线程仅等待已发出信号的回调。然后,线程池回调等待线程将定期检查是否有已发出信号的回调。
这个等待线程最终会调用QueueUserWorkItem,以便在线程池线程上执行已通知的回调函数。

根据我在 RegisterWaitForSingleObject 文档中的理解,线程池中的一个线程将等待句柄被信号激发,但不一定是实际执行工作的线程。 - Tudor
如果我执行50次RegisteredWaitHandle,那么仍然会有一个“检查线程”始终检查信号状态,但是其他n-1个线程池线程空闲,对吗? - Royi Namir
是的,这就是所有这些背后的想法。在Windows上,您可以一次等待多达64(MAXIMUM_WAIT_OBJECTS)个线程。如果您有更多线程,则会为每64个线程增加一个额外的等待线程。 - Alois Kraus

3

Thread.SleepRegisteredWaitHandle工作在不同的层次上。让我试着澄清一下:

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

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

我不熟悉实际的API,但我认为RegisteredWaitHandle将告诉线程池调度程序在需要时调用工作线程 - 您自己的线程没有被"绑定",可以继续工作或返回到线程池。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接