SpinWait vs Sleep warten. Welches verwenden?

75

Ist es effizient zu

SpinWait.SpinUntil(() => myPredicate(), 10000)

für eine Zeitüberschreitung von 10000ms

oder

Ist es effizienter, Thread.SleepPolling für dieselbe Bedingung zu verwenden ? Zum Beispiel etwas in Anlehnung an die folgende SleepWaitFunktion:

public bool SleepWait(int timeOut)
{
    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start();
    while (!myPredicate() && stopwatch.ElapsedMilliseconds < timeOut)
    {
       Thread.Sleep(50)
    }  
    return myPredicate()
}

Ich mache mir Sorgen, dass die gesamte Ausbeute von SpinWait möglicherweise kein gutes Nutzungsmuster darstellt, wenn es sich um Zeitüberschreitungen über 1 Sekunde handelt. Ist das eine gültige Annahme?

Welchen Ansatz bevorzugen Sie und warum? Gibt es einen anderen noch besseren Ansatz?


Update - Spezifischer werden:

Gibt es eine Möglichkeit, BlockingCollection Pulse zu einem schlafenden Thread zu machen, wenn die begrenzte Kapazität erreicht ist? Ich vermeide es lieber, viel zu warten, wie Marc Gravel vorschlägt.

Anastasiosyal
quelle

Antworten:

50

Der beste Ansatz besteht darin, einen Mechanismus zu haben, um aktiv zu erkennen, dass das Ding wahr wird (anstatt passiv abzufragen, ob es wahr geworden ist ). Dies kann jede Art von Wartezeit sein, oder vielleicht eine Taskmit Waitoder eine event, die Sie abonnieren können, um sich zu lösen. Wenn Sie diese Art von "Warten, bis etwas passiert" ausführen, ist dies natürlich immer noch nicht so effizient wie das einfache Ausführen der nächsten Arbeit als Rückruf , was bedeutet: Sie müssen keinen Thread zum Warten verwenden. Taskhat ContinueWithdafür, oder Sie können die Arbeit einfach in einem erledigen, eventwenn es gefeuert wird. Das eventist wahrscheinlich der einfachste Ansatz, je nach Kontext.TaskEs bietet jedoch bereits fast alles, worüber Sie hier sprechen, einschließlich der Mechanismen "Warten mit Zeitüberschreitung" und "Rückruf".

Und ja, 10 Sekunden lang zu drehen ist nicht großartig. Wenn Sie so etwas wie Ihren aktuellen Code verwenden möchten und Grund zur Erwartung einer kurzen Verzögerung haben, aber eine längere SpinWaiteinplanen müssen - vielleicht für (sagen wir) 20 ms, und Sleepfür den Rest verwenden?


Zu dem Kommentar; Hier ist, wie ich einen "Ist es voll" -Mechanismus einbinden würde:

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

mit, im Code "Zurück in die Sammlung setzen":

if(CollectionIsFull) {
    lock(syncLock) {
        if(CollectionIsFull) { // double-check with the lock
            Monitor.PulseAll(syncLock);
        }
    }
}
Marc Gravell
quelle
2
Danke, ich mag deine Antwort, das wäre in der Tat schön. Angenommen, Sie verfolgen die Verwendung einiger Ressourcen über eine BlockingCollection. Sie werden verwendet (und aus der Sammlung entfernt) und in die Sammlung zurückgebracht, wenn sie wieder zur Wiederverwendung verfügbar sind. Ein Herunterfahren kann nur erfolgen, wenn alle diese Elemente in die Sammlung zurückgekehrt sind. Wie würden Sie vorgehen, um zu signalisieren, dass ein Herunterfahren durchgeführt werden kann (dh die Sammlung ist wieder voll), außer wenn Sie mit dem Warten beschäftigt sind?
Anastasiosyal
@Anastasiosyal Ich würde die Sammlung kapseln und den Wrapper etwas tun lassen, wenn er voll ist. Eigentlich würde ich wahrscheinlich ein Monitordafür verwenden (wird ein Beispiel hinzufügen)
Marc Gravell
Fügen Sie im Destruktor einer 'ObjectPool'-Klasse, die von BlockingQueue abstammt, alle Objekte einzeln aus der BlockingCollection ab und entsorgen Sie sie (da dies C # ist, setzen Sie den abgerufenen Verweis auf nil), bis die Anzahl der Objekte angezeigt wird ist gleich der Pooltiefe. Alle gepoolten Objekte wurden dann entsorgt, sodass Sie die Warteschlange entsorgen und vom dtor zurückkehren können, um die Abschaltsequenz fortzusetzen. Kein Polling / Busywait erforderlich! Möglicherweise möchten Sie eine Zeitüberschreitung für die Aufnahme festlegen, damit ein durchgesickertes Objekt schließlich eine Ausnahme (oder etwas anderes) auslöst.
Martin James
@MartinJames - Das ist in den meisten Fällen ein gültiger Kommentar. In diesem Fall wird der Besitzer der Sammlung nicht zerstört, sondern in einen Pool zurückgebracht (denken Sie an ein Herunterfahren der Sitzung und die Sitzung wird an einen Pool zurückgegeben)
Anastasiosyal
2
@ Anastasiosyal Ich entschuldige mich - es ist Monitor.Wait- aber nein: es wird nicht zum Stillstand kommen; Dies ist die normale und häufig verwendete Technik zur Verwendung eines Monitors als Signal. Waitgibt die Sperre für die Dauer frei und erwirbt die Sperre erneut, bevor entweder true oder false zurückgegeben wird.
Marc Gravell
130

In .NET 4 SpinWaitführt das CPU-intensive Drehen für 10 Iterationen durch, bevor es nachgibt. Es kehrt jedoch nicht unmittelbar nach jedem dieser Zyklen zum Anrufer zurück. Stattdessen wird Thread.SpinWaitaufgerufen, für einen festgelegten Zeitraum über die CLR (im Wesentlichen das Betriebssystem) zu drehen. Dieser Zeitraum beträgt anfangs einige zehn Nanosekunden, verdoppelt sich jedoch mit jeder Iteration, bis die 10 Iterationen abgeschlossen sind. Dies ermöglicht Klarheit / Vorhersagbarkeit in der gesamten Spinning-Phase (CPU-intensiv), die das System entsprechend den Bedingungen (Anzahl der Kerne usw.) abstimmen kann. Wenn SpinWaites zu lange in der Phase der Spinausbeute bleibt, wird es regelmäßig in den Ruhezustand versetzt, damit andere Threads fortfahren können ( weitere Informationen finden Sie im Blog von J. Albahari ). Dieser Prozess hält garantiert einen Kern beschäftigt ...

So SpinWaitbegrenzt die CPU-intensive Spinnen zu einer festgelegten Anzahl von Iterationen, wonach es auf jedem Spin seine Zeitscheibe ergibt (durch tatsächlich Aufruf Thread.Yieldund Thread.Sleep), dessen Ressourcenverbrauch zu senken. Es erkennt auch, ob der Benutzer eine Single-Core-Maschine ausführt, und gibt in jedem Zyklus nach, wenn dies der Fall ist.

Mit Thread.Sleepdem Thread ist blockiert. Dieser Prozess ist jedoch in Bezug auf die CPU nicht so teuer wie der oben beschriebene.

Mond Ritter
quelle
Dies wird von Albaharis Website kopiert. Es sollte verwiesen werden!
Georgiosd
1
Es wird nicht wörtlich kopiert, sondern wurde von seiner Seite beeinflusst, weshalb ich auf seinen Blog verwiesen habe.
MoonKnight
1
Vielen Dank für die Erklärung, es ist wirklich informativ.
T Kambi