SpinWait проти очікування сну. Який із них використовувати?


76

Чи ефективно це

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 сплячою ниткою, коли вона досягне обмеженої потужності? Я радше уникаю зайнятих очікувань, як пропонує Марк Гравел.

Відповіді:


51

Кращий підхід полягає в якому - то механізм , щоб активно виявити річ стає справжньою (а не пасивно опитування за це то , стане справжнім); це може бути будь-яка ручка очікування, або, може бути, за 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
Дякую, мені подобається ваша відповідь, це справді було б непогано. Припустимо, ви відстежуєте використання деяких ресурсів за допомогою BlockingCollection. Вони звикають (і вилучаються з колекції) і повертаються до колекції, коли вони знову стануть доступними для повторного використання. Вимкнення може відбутися лише тоді, коли всі вони повернулись до колекції. Як би ви просигналізували про те, що вимкнення може тривати (тобто колекція знову заповнена), крім зайнятого очікуванням?
Анастасіосял

@Anastasiosyal Я б інкапсулював колекцію і попросив обгортку щось зробити, коли вона заповнена; насправді, я б, мабуть, використав Monitorдля цього (додам приклад)
Марк Гравелл

У деструкторі класу 'ObjectPool', що спускається з BlockingQueue, витягніть усі об'єкти з BlockingCollection по одному і утилізуйте їх (оскільки це C #, встановіть отримане посилання на нуль), поки кількість об'єктів не з'явиться дорівнює глибині басейну. Потім усі об’єднані об’єкти були утилізовані, тому ви можете розподілити чергу та повернутися з dtor, щоб продовжити послідовність вимкнення. Не потрібно опитування / зайняте очікування! Можливо, ви захочете встановити певний тайм-аут для прийому, щоб об’єкт, що просочився, з часом викликав виняток (або щось інше).
Мартін Джеймс

@MartinJames - це слушний коментар для більшості випадків. У цьому випадку власника колекції не знищують, а повертають до пулу (подумайте про вимкнення сеансу, а сеанс повертається до пулу)
Анастасіосял,

2
@Anastasiosyal мої вибачення - це так Monitor.Wait- але ні: це не заглохне; це звичайна та добре використовувана техніка використання монітора як сигналу. Waitзвільняє блокування на час, а потім знову отримує блокування, перш ніж повернути true або false.
Марк Гравелл

130

У .NET 4 SpinWaitвиконується процесор з інтенсивним процесором протягом 10 ітерацій до отримання. Але він не повертається до абонента відразу після кожного з цих циклів; натомість він вимагає Thread.SpinWaitобертання через CLR (по суті ОС) протягом встановленого періоду часу. Цей період часу спочатку становить кілька десятків наносекунд, але подвоюється з кожною ітерацією, поки не завершиться 10 ітерацій. Це забезпечує чіткість / передбачуваність у фазі загального часу, витраченого на обертання (інтенсивний процесор), який система може налаштувати відповідно до умов (кількість ядер тощо). Якщо він SpinWaitзалишається у фазі віджиму занадто довго, він періодично буде спати, щоб дозволити іншим потокам продовжувати (див . Блог Дж. Альбахарі для отримання додаткової інформації). Цей процес гарантує зайнятість ядра ...

Отже, SpinWaitобмежує обертання процесора інтенсивним використанням заданою кількістю ітерацій, після чого він дає свій час у кожному обертанні (фактично викликаючи Thread.Yieldта Thread.Sleep), зменшуючи споживання ресурсів. Він також виявить, чи користувач працює на одній основній машині, і дасть вихід на кожному циклі, якщо це так.

При Thread.Sleepцьому нитка заблокована. Але цей процес не буде таким дорогим, як описаний вище, з точки зору процесора.


Це скопійовано з веб-сайту Альбахарі. На нього слід посилатись!
georgiosd

1
Він не копіюється дослівно, але на нього вплинув його сайт, отже, чому я посилався на його блог.
MoonKnight

1
Дякую за пояснення, це справді інформативно.
T Kambi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.