Thread.sleep vs Monitor.Wait vs RegisteredWaitHand

2019-04-20 18:30发布

问题:

(the following items has different goals , but im interesting knowing how they "PAUSEd")

questions

Thread.sleep - Does it impact performance on a system ?does it tie up a thread with its wait ?

what about Monitor.Wait ? what is the difference in the way they "wait"? do they tie up a thread with their wait ?

what about RegisteredWaitHandle ? This method accepts a delegate that is executed when a wait handle is signaled. While it’s waiting, it doesn’t tie up a thread.

so some thread are paused and can be woken by a delegate , while others just wait ? spin ?

can someone please make things clearer ?

edit

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

回答1:

Both Thread.Sleep and Monitor.Wait put the thread in the WaitSleepJoin state:

WaitSleepJoin: The thread is blocked. This could be the result of calling Thread::Sleep or Thread::Join, of requesting a lock — for example, by calling Monitor::Enter or Monitor::Wait — or of waiting on a thread synchronization object such as ManualResetEvent.

RegisteredWaitHandle is obtained by calling RegisterWaitForSingleObject and passing a WaitHandle. Generally all descendants of this class use blocking mechanisms, so calling Wait will again put the thread in WaitSleepJoin (e.g. AutoResetEvent).

Here's another quote from MSDN:

The RegisterWaitForSingleObject method checks the current state of the specified object's WaitHandle. If the object's state is unsignaled, the method registers a wait operation. The wait operation is performed by a thread from the thread pool. The delegate is executed by a worker thread when the object's state becomes signaled or the time-out interval elapses.

So a thread in the pool does wait for the signal.



回答2:

Regarding ThreadPool.RegisterWaitForSingleObject, this does not tie up a thread per registration (pooled or otherwise). You can test this easily: run the following script in LINQPad which calls that method 20,000 times:

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...
}

If that code tied up 20,000 threads for the duration of the 5-second "wait", it couldn't possibly work.

Edit - in response to:

"this is a proof. but is there still a single thread which checks for signals only ? in the thread pool ?"

This is an implementation detail. Yes, it could be implemented with a single thread that offloads the callbacks to the managed thread pool, although there's no guarantee of this. Wait handles are ultimately managed by operating system, which will most likely trigger the callbacks, too. It might use one thread (or a small number of threads) in its internal implementation. Or with interrupts, it might not block a single thread. It might even vary according to the operating system version. This is an implementation detail that's of no real relevance to us.



回答3:

While it's true RegisterWaitForSingleObject creates wait threads, not every call creates one.

From MSDN:

New wait threads are created automatically when required

From Raymond Chen's blog:

...instead of costing a whole thread, it costs something closer to (but not exactly) 1/64 of a thread

So using RegisterWaitForSingleObject is generally preferable to creating your own wait threads.



回答4:

Thread.Sleep and RegisteredWaitHandle work at different levels. Let me try and clear it up:

Processes have multiple threads, which execute simultaneously (depending on the OS scheduler). If a thread calls Thread.Sleep or Monitor.Wait, it doesn't spin - it is put to WaitSleepJoin state, and the CPU is given to other threads.

Now, when you have many simultaneous work items, you use a thread pool - a mechanism which creates several threads, and uses its own understanding of work items to dispatch calls to its threads. In this models, worker threads are called from the thread pool dispatcher to do some work, and then return back to the pool. If a worker thread calls a blocking operation - like Thread.Sleep or Monitor.Wait - the this thread is "tied up", since the thread pool dispatcher can't use it for additional work items.

I'm not familiar with the actual API, but I think RegisteredWaitHandle would tell the thread pool dispatcher to call a worker thread when needed - and your own thread is not "tied up", and can continue its work or return to the thread pool.



回答5:

ThreadPool.g RegisterWaitForSingleObject does call in its native implementation ultimately QueueUserAPC. See rotor sources (sscli20\clr\src\vm\win32threadpool.cpp(1981)). Unlike Wait Thread.Sleep your thread will not be put to a halt when you use RegisterWaitForSingleObject.

Instead for this thread a FIFO queue with user mode callbacks is registered which will be called when the thread is in an alertable state. That means you can continue to work and when your thread is blocked the OS will work on the registered callbacks giving your thread do to the opportunity to do something meaningful while it is waiting.

Edit1:

To complete the analysis. On the thread that did call RegisterWaitForSingleObject a callback is called on the thread when it is in an alertable state. Once this happens the the thread that did call RegisterWaitForSingleObject will execute a CLR callback that does register another callback which is processed by a thread pool callback wait thread which is only there to wait for signaled callbacks. This thread pool callback wait thread will then check in regular intervals for signaled callbacks.

This wait thread does finally call QueueUserWorkItem for the signalled callback to be executed on a thread pool thread.