【问题标题】:SwitchToThread/Thread.Yield vs. Thread.Sleep(0) vs. Thead.Sleep(1)SwitchToThread/Thread.Yield 与 Thread.Sleep(0) 与 Thread.Sleep(1)
【发布时间】:2010-11-27 16:05:21
【问题描述】:

我正在尝试编写最终的“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
}

【问题讨论】:

  • 您介意我问一下您认为这个库将解决哪类问题吗?
  • if(!Thread.Yield()) { Thread.Sleep(1); } 怎么样(感谢 stackoverflow.com/questions/1383943/… )?另请注意,您的代码需要try...finally 用于timeBeginPeriod()timeEndPeriod(),以免在Thread.Sleep() 引发异常时中断。

标签: .net multithreading concurrency


【解决方案1】:

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

假设你有这样的代码:

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

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

改为:

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

我们正在指示 CPU 将一些资源分配给另一个线程。

SpinWait 不影响操作系统的线程调度。

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

【讨论】:

  • 超线程处理器与双核处理器有何不同?顺便说一句,我实现了“Ultimate Yield”来隐藏实现细节,显然我仍然需要考虑在某个场景下哪个 yield 最有意义。
  • 在双核处理器中,每个核都有自己的状态和执行资源。大多数情况下,每个内核都可以被视为自己独特的 CPU,它们只是共享一个包。超线程 CPU 具有重复状态,但没有执行资源。在操作系统看来,它是多个 CPU。处理器上只有有限的执行资源,它在已调度的不同线程之间切换。确实存在超线程和多核 - 您可以购买四核、超线程 Core i7 处理器。 4 个核心,8 个可运行线程。
  • 是的,我刚刚做到了。我对如何在我的方法中使用 Thread.SpinWait 比超线程的实际含义更感兴趣。例如,添加“ThreadYieldTarget.AnyThreadOnHyperThreadedProcessor”并调用 Thread.SpinWait(1) 是否有意义?
  • 不,那没有意义。 SpinWait 主要用于实现自旋锁。您不应该将其视为收益。
  • 如果你不知道 SpinWait 做了什么(详细)你不应该考虑使用它...
【解决方案2】:

Jeff Moser (http://www.moserware.com/2008/09/how-do-locks-lock.html) 的文章“锁如何锁定”可以深入了解 SpinWait 的机制。引用文件:

它到底在做什么?看着 转子的 clr/src/vm/comsynchronizable.cpp 给出 我们的现实:

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

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

} FIMPLEND

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

#define YieldProcessor() __asm { rep nop }

这是一个“重复无操作”程序集 操作说明。这也是众所周知的 Intel指令集手册为“PAUSE - 自旋循环提示。”这意味着 CPU 知道自旋等待 我们想要完成。

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

【讨论】:

    【解决方案3】:

    SpinWait 旨在等待不产生当前时间片

    它适用于您知道自己想在很短的时间内做某事的情况,因此失去时间片会过度。

    我的印象是 Thread.Yield(x) 对于任何 x 值

    【讨论】:

      【解决方案4】:

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

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

      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 制作

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-26
        • 2012-07-13
        • 2013-04-28
        • 2012-03-24
        • 2018-09-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多