.net 切换到线程/Thread.Yield vs.Thread.Sleep(0)vs.Thead.Sleep(1)

.net 切换到线程/Thread.Yield vs.Thread.Sleep(0)vs.Thead.Sleep(1),.net,multithreading,concurrency,.net,Multithreading,Concurrency,我正在尝试编写最终的“Yield”方法,以便将当前的时间片提供给其他线程。到目前为止,我发现有几种不同的方法可以使线程产生分配的时间片。我只是想确保我正确地解释了它们,因为文档不是很清楚。因此,从我对stackoverflow、MSDN和各种博客文章的了解来看,以下选项都有不同的优点/缺点: SwitchToThread

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

SwitchToThread
Thread.Yield
[.NET 4 Beta 1]:屈服于同一处理器上的任何线程

  • 优点:速度大约是原来的两倍
    Thread.Sleep(0)
  • 缺点:只允许线程使用 在同一处理器上
线程。睡眠(0)
让位于任何处理器上具有相同或更高优先级的任何线程

  • 优点:比以前快
    Thread.Sleep(1)
  • 缺点:只允许线程使用 同等或更高优先级的
Thread.Sleep(1)
:让位于任何处理器上的任何线程

  • 优点:对任何线程都有让步 任何处理器
  • 缺点:最慢的选择 (
    Thread.Sleep(1)
    通常 如果需要,将螺纹悬挂约15毫秒
    timeBeginPeriod
    /
    timeEndPeriod
    [win32]未使用)
那Thread.SpinWait呢?这可以用来产生线程的时间片吗?如果没有,它的用途是什么

我还发现了我遗漏或错误解释的其他内容。如果你能纠正/补充我的理解,我将不胜感激

到目前为止,我的收益率方法是这样的:

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
}
公共静态类线程
{
[DllImport(“kernel32.dll”)]
静态外部bool SwitchToThread();
[DllImport(“winmm.dll”)]
内部静态外部单元时间起始周期(单元周期);
[DllImport(“winmm.dll”)]
内部静态外部uint timeEndPeriod(uint period);
///将当前线程的时间片生成到指定的目标线程
公共静态无效YieldTo(ThreadYieldTarget ThreadYieldTarget)
{
交换机(ThreadyEldTarget){
案例ThreadyEldTarget。无:
打破
case ThreadyEldTarget.AnyThreadOnAnyProcessor:
timeBeginPeriod(1);//将睡眠时间减少到实际1ms,而不是系统时间片为15ms左右
系统线程线程睡眠(1);
timeEndPeriod(1);//撤消
打破
case ThreadyEldTarget.SameorhigherPriorityThreadOnAny处理器:
系统线程线程睡眠(0);
打破
case ThreadyEldTarget.AnyThreadOnSameProcessor:
SwitchToThread();
打破
默认值:抛出新ArgumentOutOfRangeException(“threadYieldTarget”);
}
}
}
公共枚举ThreadYieldTarget
{
///操作系统将决定何时中断线程
没有一个
///将时间片让给任何处理器上的任何其他线程
任何线程,任何处理器,
///在任何处理器上,将时间片提供给具有相同或更高优先级的其他线程
SameorhigherpriorityThreadOnAny处理器,
///将时间片让给同一处理器上的任何其他线程
AnyThreadOnSameProcessor
}

SpinWait设计为在不产生当前时间片的情况下等待

它是为那些你知道你想在很短的时间内完成某件事的情况而设计的,所以失去时间片将是过度的


我的印象是线程。任何x值的收益率(x)<线程量是等价的,包括零,尽管我没有这方面的基准。

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

假设您有如下代码:

while (!foo) {} // Spin until foo is set.
如果此线程在超线程处理器上的线程上运行,则它正在消耗可用于处理器上运行的其他线程的处理器资源

改为:

while (!foo) {Thread.SpinWait(1);} 
我们指示CPU向另一个线程提供一些资源

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

对于你关于“最终收益率”的主要问题,这在很大程度上取决于你的情况——如果你不澄清为什么你想让一条线收益,你将无法得到一个好的答案。在我看来,让处理器运行的最好方法是让线程进入等待状态,只有在有工作要做时才唤醒。其他任何事情都只是浪费CPU时间。

Jeff Moser()的文章“锁是如何锁定的”可以提供一些关于SpinWait机制的内部信息。引用该文件:

它到底在做什么?看着 转子的 clr/src/vm/comsynchronizable.cpp提供 美国面对现实:

FCIMPL1(void,ThreadNative::SpinWait,int迭代) { 包装合同; 静态契约容忍

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

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

#定义YieldProcessor()

这是一个“重复无操作”组件 指示这在世界上也是众所周知的 英特尔指令集手册为“暂停” -这意味着CPU知道等待它的自旋 我们想要完成

相关的:

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

(!)不要太认真地对待这个评测!这只是为了用数字说明上述答案,并粗略比较magnitu
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));
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