Подтвердить что ты не робот

SwitchToThread/Thread.Yield против Thread.Sleep(0) против Thead.Sleep(1)

Я пытаюсь написать окончательный метод "Выход", чтобы дать текущее срез времени другим потокам. До сих пор я обнаружил, что существует несколько способов сделать поток доступным для выделенного временного фрагмента. Я просто хочу убедиться, что я правильно их интерпретирую, так как документация не очень ясна. Итак, из того, что я прочитал в 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
}
4b9b3361

Ответ 1

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

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

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

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

Изменяя на:

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

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

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

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

Ответ 2

В статье "Как блокирует блокировки" Джеффа Мозера (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}

Это сборка "повторить не-op" инструкция. Он также известен в Руководство по набору инструкций Intel в качестве "ПАУЗА" - Подсказка с помощью Spin Loop. "Это означает, что ЦП знает о ожидании вращения, ожидая, что мы хотим выполнить.

по теме: 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