SwitchToThread / Thread.Yield против Thread.Sleep (0) против Thead.Sleep (1) - PullRequest
38 голосов
/ 12 сентября 2009

Я пытаюсь написать окончательный метод "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 ]

12 голосов
/ 12 сентября 2009

SpinWait полезен на многопоточных процессорах. Благодаря гиперпоточности несколько запланированных потоков ОС могут выполняться на одном физическом процессоре, совместно используя ресурсы процессора. SpinWait указывает процессору, что вы не выполняете никакой полезной работы и что он должен запускать код из другого логического процессора. Как следует из названия, он обычно используется во время вращения.

Предположим, у вас есть такой код:

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

Если этот поток выполняется в потоке на многопоточном процессоре, он потребляет ресурсы процессора, которые можно использовать для других потоков, работающих на процессоре.

Меняя на:

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

Мы указываем ЦПУ выделить некоторые ресурсы другому потоку.

SpinWait не влияет на планирование потоков в ОС.

На ваши основные вопросы об «окончательной доходности» она сильно зависит от вашей ситуации - вы не сможете получить хороший ответ, не объяснив, почему вы хотите получить поток. С моей точки зрения, лучший способ вернуть процессор - это заставить поток войти в состояние ожидания и просыпаться только тогда, когда есть над чем работать. Все остальное просто тратит процессорное время.

5 голосов
/ 10 января 2011

Статья Джеффа Мозера "Как блокировка замков" (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();

} FCIMPLEND

Дальнейшее погружение показывает, что "YieldProcessor" - это макрос:

# define YieldProcessor () __asm ​​{rep nop}

Это сборка «повторить запрет» инструкция. Это также известно в Инструкция по набору инструкций Intel как "ПАУЗА" - Spin Loop Hint. "Это означает, что процессор знает о ожидании вращения, что мы хотим достичь.

Связанный: http://msdn.microsoft.com/en-us/library/ms687419(VS.85).aspx http://www.moserware.com/2008/09/how-do-locks-lock.html#lockfn7

4 голосов
/ 12 сентября 2009

SpinWait предназначен для ожидания без выдачи текущего временного интервала

Он предназначен для ситуаций, когда вы знаете, что хотите что-то сделать в очень короткое время, поэтому потеря временного интервала приведет к потере времени.быть чрезмерным.

У меня сложилось впечатление, что Thread.Yield (x) для любого значения x <квант потока был эквивалентен, включая ноль, хотя у меня нет эталонных тестов на этот счет. </p>

2 голосов
/ 24 марта 2016

В дополнение к другим ответам, вот некоторые цифры профилирования.

(!) Не принимайте это профилирование слишком серьезно! Сделано только для иллюстрации приведенных выше ответов в цифрах и приблизительного сравнения величин.

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));

Результаты вращения петель за ~ 5 с:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...