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
}
Ответы
Ответ 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