SwitchToThread / Thread.Yield与Thread.Sleep(0)vs. Thread.Sleep(1)

时间:2009-09-11 22:50:14

标签: .net multithreading concurrency

我正在尝试编写最终的“Yield”方法,以将当前时间片提供给其他线程。到目前为止,我发现有几种不同的方法可以使线程产生分配的时间片。我只是想确保我正确地解释它们,因为文档不是很清楚。因此,根据我在stackoverflow,MSDN和各种博客文章中所读到的内容,存在以下选项,它们都有不同的优点/缺点:

SwitchToThread [win32] / Thread.Yield [.NET 4 Beta 1]:产生同一处理器上的任何线程

  • 优势:大约快两倍 Thread.Sleep(0)
  • 缺点:仅产生线程 在同一个处理器上

Thread.Sleep(0)会在任何处理器上产生相同或更高优先级的任何线程

  • 优势:快于 Thread.Sleep(1)
  • 缺点:仅产生线程 相同或更高的优先级

Thread.Sleep(1) :屈服于任何处理器上的任何线程

  • 优势:产生任何线程 任何处理器
  • 缺点:选择最慢 (Thread.Sleep(1)通常会 如果,挂起约15毫秒的线程 timeBeginPeriod / timeEndPeriod [win32]未使用)

Thread.SpinWait怎么样?可以用它来产生线程的时间片吗?如果没有,它用于什么?

我还有其他一些我错过或错误解释的东西。如果你能纠正/增加我的理解,我将不胜感激。

这就是我的Yield方法到目前为止的样子:

public static class Thread
{
    [DllImport("kernel32.dll")]
    static extern bool SwitchToThread();

    [DllImport("winmm.dll")]
    internal static extern uint timeBeginPeriod(uint period);

    [DllImport("winmm.dll")]
    internal static extern uint timeEndPeriod(uint period);

    /// <summary>  yields time slice of current thread to specified target threads </summary>
    public static void YieldTo(ThreadYieldTarget threadYieldTarget)
    {
        switch (threadYieldTarget) {
            case ThreadYieldTarget.None: 
                break; 
            case ThreadYieldTarget.AnyThreadOnAnyProcessor:
                timeBeginPeriod(1); //reduce sleep to actually 1ms instead of system time slice with is around 15ms
                System.Threading.Thread.Sleep(1); 
                timeEndPeriod(1); //undo
                break;
            case ThreadYieldTarget.SameOrHigherPriorityThreadOnAnyProcessor:
                System.Threading.Thread.Sleep(0); 
                break;
            case ThreadYieldTarget.AnyThreadOnSameProcessor:
                SwitchToThread();
                break;
            default: throw new ArgumentOutOfRangeException("threadYieldTarget");
        }
    }
}

public enum ThreadYieldTarget
{
    /// <summary>  Operation system will decide when to interrupt the thread </summary>
    None,
    /// <summary>  Yield time slice to any other thread on any processor </summary>
    AnyThreadOnAnyProcessor,
    /// <summary>  Yield time slice to other thread of same or higher piority on any processor </summary>
    SameOrHigherPriorityThreadOnAnyProcessor,
    /// <summary> Yield time slice to any other thread on same processor </summary>
    AnyThreadOnSameProcessor
}

4 个答案:

答案 0 :(得分:12)

SpinWait在超线程处理器上很有用。通过超线程,多个OS调度线程可以在同一物理处理器上运行,共享处理器资源。 SpinWait向处理器指示您没有做任何有用的工作,并且它应该从不同的逻辑CPU运行代码。顾名思义,它通常在您旋转时使用。

假设您有以下代码:

while (!foo) {} // Spin until foo is set.

如果此线程在超线程处理器上的线程上运行,则会消耗可用于处理器上运行的其他线程的处理器资源。

改为:

while (!foo) {Thread.SpinWait(1);} 

我们正在向CPU指示为其他线程提供一些资源。

SpinWait不会影响线程的OS调度。

关于“终极收益”的主要问题,这在很大程度上取决于您的情况 - 如果没有说明您希望线程产生的原因,您将无法得到一个好的答案。从我的角度来看,产生处理器的最好方法是让线程进入等待状态,只有在有工作要做时才会醒来。其他任何事情只是在浪费CPU时间。

答案 1 :(得分:5)

Jeff Moser撰写的文章“Locks Lock如何”(http://www.moserware.com/2008/09/how-do-locks-lock.html)可以给出一些关于SpinWait机制的内容。引用该文件:

  

究竟是做什么的?看着   转子   clr / src / vm / comsynchronizable.cpp给出   我们现实:

     

FCIMPL1(void,ThreadNative :: SpinWait,int iterations)      {       WRAPPER_CONTRACT;       STATIC_CONTRACT_SO_TOLERANT;

for(int i = 0; i < iterations; i++)
    YieldProcessor();
     

}      FCIMPLEND

     

进一步潜水表明   “YieldProcessor”就是这个宏:

     

#define YieldProcessor()__ asm {rep nop}

     

这是一个“重复无操作”程序集   指令。它也是众所周知的   英特尔指令集手册为“PAUSE    - Spin Loop Hint。“这意味着CPU知道旋转等待   我们想要完成。

相关: http://msdn.microsoft.com/en-us/library/ms687419(VS.85).aspx http://www.moserware.com/2008/09/how-do-locks-lock.html#lockfn7

答案 2 :(得分:4)

SpinWait设计为等待而不产生当前时间片

它适用于你知道你想在很短的时间内做某事的情况,所以失去你的时间片会过度。

对于任何x&lt;的值,我都在Thread.Yield(x)的印象之下。线程量子是等价的,包括零,虽然我没有这个效果的基准。

答案 3 :(得分:2)

除了其他答案,这里还有一些分析数字。

(!)不要太认真地对待这个分析!仅仅是为了在数字上说明上述答案并粗略地比较值的大小。

static void Profile(Action func)
    {
        var sw = new Stopwatch();
        var beginTime = DateTime.Now;
        ulong count = 0;
        while (DateTime.Now.Subtract(beginTime).TotalSeconds < 5)
        {
            sw.Start();
            func();
            sw.Stop();
            count++;
        }
        Console.WriteLine($"Made {count} iterations in ~5s. Total sleep time {sw.ElapsedMilliseconds}[ms]. Mean time = {sw.ElapsedMilliseconds/(double) count} [ms]");
    }

        Profile(()=>Thread.Sleep(0));
        Profile(()=>Thread.Sleep(1));
        Profile(()=>Thread.Yield());
        Profile(()=>Thread.SpinWait(1));

旋转循环的结果~5s:

Function   | CPU % | Iters made |  Total sleep  | Invoke 
           |       |            |  time [ms]    | time [ms]
===================================================================== 
Sleep(0)   | 100 0 | 2318103    | 482           | 0.00020
Sleep(1)   |  6  0 | 4586       | 5456          | 1.08971 
Yield()    | 100 0 | 2495220    | 364           | 0.00010
SpinWait(1)| 100 0 | 2668745    | 81            | 0.00003

使用Mono 4.2.3 x86_64制作