简体   繁体   English

Thread.Yield与WaitOne

[英]Thread.Yield vs. WaitOne

From what I understand, Thread.Yield could be used instead of WaitOne and ManualResetEvent for the purpose of thread signalling. 据我了解, Thread.Yield发出线程信号的目的,可以使用Thread.Yield代替WaitOneManualResetEvent

Although I have not come across a document explaining the exact behavior of WaitOne behind the scenes, I assume that it places the the thread in Wait state and tells the OS scheduler to check if the ManualResetEvent is set every time it is this thread's turn in the queue. 尽管我还没有看到一个文档来解释WaitOne在幕后的确切行为,但我认为它将线程置于Wait状态,并告诉OS调度程序每次在该线程轮到时检查是否设置了ManualResetEvent 。队列。 If not set, scheduler does not do the context switch and skips to another thread. 如果未设置,则调度程序不执行上下文切换,而是跳到另一个线程。 If set, scheduler puts the thread into running state so the code after WaitOne starts to execute. 如果设置,则调度程序将线程置于运行状态,以便WaitOne之后的代码开始执行。

On the other hand, using Thread.Yield will cause the context switch no matter the state of the ManualResetEvent and do the check afterwards. 另一方面,无论ManualResetEvent的状态如何,使用Thread.Yield都将导致上下文切换,然后进行检查。

Is my understanding correct? 我的理解正确吗? Is there a document that explains the inner workings of WaitOne ? 是否有文档解释WaitOne的内部工作原理?

PS: Here are the sample codes of both versions for demonstration purposes: PS:这是用于演示目的的两个版本的示例代码:

var signal = new ManualResetEvent(false);
new Thread(() =>
{
    Console.WriteLine("Waiting for signal...");
    signal.WaitOne();
    signal.Dispose();
    Console.WriteLine("Got signal!");
}).Start();
Thread.Sleep(2000);
signal.Set(); // "Open" the signal

bool signal = false;
new Thread(() =>
{
    Debug.WriteLine("Waiting for signal...");
    while(signal == false)
    {
        Thread.Yield();
    }
    Debug.WriteLine("Got signal!");
}).Start();
Thread.Sleep(2000);
signal = true; ; // "Open" the signal

First off, Hans' comments are completely correct: you are inventing your own spinwait, badly. 首先,汉斯的评论是完全正确的:您正在发明自己的旋转等待,非常糟糕。 Don't do that! 不要那样做!

That said, your question is not about whether you should re-implement WaitOne, but rather, how WaitOne was implemented by people who did not have it, because it was not yet written . 就是说,您的问题不是关于是否应该重新实现WaitOne,而是因为没有编写WaitOne的人是如何实现WaitOne的。 It is entirely reasonable to consider this question; 考虑这个问题是完全合理的; such functions are not magical and were implemented by humans, so how did they do so? 这些功能不是神奇的,而是由人类实现的,那么它们是如何做到的呢?

That's an implementation detail and I do not have the source code for the runtime handy; 这是一个实现细节,我没有方便的运行时源代码。 the actual implementation is in a native function called WaitOneNative . 实际的实现是在名为WaitOneNative的本机函数中WaitOneNative However, I can give you a few thoughts. 但是,我可以给您一些想法。

First, you are right to note that Thread.Yield is a more primitive operation, and therefore it could be used as part of a strategy to build a higher-level operation like WaitOne . 首先,您正确地注意到Thread.Yield是更原始的操作,因此可以将其用作构建诸如WaitOne类的更高级别操作的策略的一部分。 But in practice it probably would not be used in the naive manner you describe, for several reasons: 但是实际上,由于以下几个原因,可能不会以您描述的幼稚方式使用它:

  • Thread.Yield does create a barrier, but it is not 100% obvious from the code that the read of the bool has not been elided, or that the write cannot be delayed. Thread.Yield确实会创建一个障碍,但是从代码中并不是100%显而易见的是没有消除对bool的读取,或者不能延迟写入。 We'd want to make very, very sure that the bool write was being picked up, and that introducing the barrier did not wreck performance. 我们要非常非常确定地确定布尔写操作,并且引入障碍不会破坏性能。

  • Thread.Yield cedes control to any ready thread on the current processor . Thread.Yield将控制权Thread.Yield当前处理器上的任何就绪线程。 What happens if there is no ready thread on the current processor? 如果当前处理器上没有就绪线程,该怎么办? Maybe ponder that. 也许考虑一下。 What keeps this code from heating up an entire CPU? 是什么使该代码无法加热整个CPU? What happens if the thread that is going to do the write is on a different processor? 如果要执行写操作的线程在不同的处理器上会发生什么? What are all the possible scenarios involving thread starvation, and so on? 涉及线程匮乏等所有可能的方案是什么?

  • Consider this scenario: We have a hyperthreaded processor with three threads, Alpha, Bravo and Charlie, and Alpha and Bravo are currently executing in the CPU. 考虑这种情况:我们有一个具有三个线程的超线程处理器,Alpha,Bravo和Charlie,并且Alpha和Bravo当前正在CPU中执行。 Alpha has 10 million nanoseconds left in its quantum, it sees that the flag is false and yields the remainder of its quantum to Charlie. Alpha的量子剩余时间为1000万纳秒,它发现该标志是错误的,并将其剩余的量子提供给Charlie。 One nanosecond later, Bravo sets the flag. 一纳秒后,Bravo设置了标志。 We just took on the entire cost of a context switch and Alpha giving up on the chance to do ten million nanoseconds of work! 我们只承担了上下文切换的全部费用,而Alpha放弃了进行一千万纳秒工作的机会! It would have been better for Alpha to spinwait and burn a few dozen of its ten million nanoseconds rather than take the enormous cost of a context switch. 对于Alpha来说,旋转等待并消耗其一千万纳秒中的几十个,而不是花费上下文切换的巨额成本会更好。 These are the sorts of scenarios you have to take into account when you are designing a new threading primitive . 在设计新的线程原语时,必须考虑这些情况 Just getting the control flow right is not good enough; 仅使控制流程正确还不够。 you make a bad decision on a hot path and you can degrade performance by a factor of thousands or millions. 您在热路径上做出错误的决定,可能会使性能降低数千或数百万。

  • And so on. 等等。

But wait, it gets worse. 但是,等等,情况变得更糟。 Are there more subtle problems that WaitOne has to solve? WaitOne是否需要解决更多细微的问题?

Sure. 当然。 The CLR has invariants that it must maintain. CLR具有必须维护的不变式。 You have to remember that the CLR was invented fundamentally as an extension to COM and the underlying implementation is deeply embedded in the COM world. 您必须记住, CLR是从根本上发明的,是对COM的扩展 ,其底层实现已深深地嵌入到COM世界中。 In particular, all the rules about marshalling still apply. 特别是,关于编组的所有规则仍然适用。 WaitOne effectively puts a thread to sleep, but that can lead to problems with the marshaller. WaitOne有效地使线程进入睡眠状态,但是这可能导致编组器出现问题。 Chris Brumme's article on this is particularly terrifying and elicidating: 克里斯·布鲁姆(Chris Brumme)在这方面的文章特别令人恐惧和困扰:

https://blogs.msdn.microsoft.com/cbrumme/2004/02/02/apartments-and-pumping-in-the-clr/ https://blogs.msdn.microsoft.com/cbrumme/2004/02/02/apartments-and-pumping-in-the-clr/

Read it, see if you can understand all of it. 阅读它,看看您是否能理解所有内容。 I've read it dozens of times since 2004, and I used to be a professional COM programmer, and I get maybe 80% of it. 自2004年以来,我已经阅读了数十次,而且我曾经是一名专业的COM程序员,我大概得到了其中的80%。 This is complicated stuff, and if you don't understand it, you can't write a correct implementation of WaitOne that meets the needs of the CLR. 这是一件很复杂的事情,如果您不了解,您将无法编写满足CLR需求的WaitOne正确实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM