SpinWait против ожидания ожидания. Какой из них использовать?
Эффективно ли это для
SpinWait.SpinUntil(() => myPredicate(), 10000)
для тайм-аута 10000 мс
или
Насколько эффективнее использовать опрос Thread.Sleep
для того же условия
Например, что-то по строкам следующей функции SleepWait
:
public bool SleepWait(int timeOut)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
while (!myPredicate() && stopwatch.ElapsedMilliseconds < timeOut)
{
Thread.Sleep(50)
}
return myPredicate()
}
Я обеспокоен тем, что все уступки SpinWait не могут быть хорошим шаблоном использования, если мы говорим о тайм-аутах в течение 1 секунды? Действительно ли это предположение?
Какой подход вы предпочитаете и почему? Есть ли еще лучший подход?
Обновить. Становится более конкретным:
Есть ли способ сделать BlockingCollection Pulse спальным потоком, когда он достигнет ограниченной емкости? Я предпочитаю избегать оживленного ожидания, поскольку Марк Гравел предлагает.
Ответы
Ответ 1
Лучший подход заключается в том, чтобы какой-то механизм активно обнаруживал предмет, который становится истинным (а не пассивный опрос, чтобы он стал истинным); это может быть любой вид wait-handle, а может быть Task
с Wait
или, возможно, event
, на который вы можете подписаться, чтобы разблокировать себя. Конечно, если вы выполняете такое "подождать, пока что-то случится", это еще не так эффективно, как просто следующий бит работы, выполненный как обратный вызов, что означает: вам не нужно используйте поток для ожидания. Task
имеет ContinueWith
для этого, или вы можете просто выполнить работу в event
при запуске. event
, вероятно, самый простой подход, в зависимости от контекста. Task
, однако, уже предоставляет большинство - все, о чем вы здесь говорите, включая механизмы ожидания с тайм-аутом и механизмы обратного вызова.
И да, вращение в течение 10 секунд не очень велико. Если вы хотите использовать что-то вроде своего текущего кода, и если у вас есть основания ожидать короткой задержки, но вам нужно использовать более длинный - возможно, SpinWait
для (скажем) 20 мс и использовать Sleep
для остальных?
Повторите комментарий; здесь, как я зацепил механизм "это полный":
private readonly object syncLock = new object();
public bool WaitUntilFull(int timeout) {
if(CollectionIsFull) return true; // I'm assuming we can call this safely
lock(syncLock) {
if(CollectionIsFull) return true;
return Monitor.Wait(syncLock, timeout);
}
}
с, в коде "вернуть обратно в коллекцию":
if(CollectionIsFull) {
lock(syncLock) {
if(CollectionIsFull) { // double-check with the lock
Monitor.PulseAll(syncLock);
}
}
}
Ответ 2
В .NET 4 SpinWait
выполняется CPU-интенсивное вращение на 10 итераций до урожая. Но он не возвращается к вызывающей стороне сразу после каждого из этих циклов; вместо этого он вызывает Thread.SpinWait
для вращения через CLR (по существу, OS) в течение заданного периода времени. Этот период времени составляет несколько десятков наносекунд, но с каждой итерацией удваивается до тех пор, пока не будут выполнены 10 итераций. Это обеспечивает ясность/предсказуемость в общей длительности отжига (интенсивность процессора), которую система может настраивать в соответствии с условиями (количество ядер и т.д.). Если SpinWait
слишком долго остается в фазе создания спина, он будет периодически спать, чтобы продолжить другие потоки (см. J. Albahari blog для больше информации). Этот процесс гарантированно сохраняет ядро занятым...
Таким образом, SpinWait
ограничивает интенсивность вращения процессора до заданного количества итераций, после чего он дает свой временной срез на каждом спине (фактически вызывая Thread.Yield
и Thread.Sleep
), снижая потребление ресурсов. Он также обнаружит, работает ли пользователь на одном ядерном компьютере и дает на каждый цикл, если это так.
С Thread.Sleep
поток заблокирован. Но этот процесс не будет таким дорогим, как выше, с точки зрения процессора.