Was macht man, wenn man keine Wait-Funktionen verwenden will, aber dennoch möchte, dass ein anderer Thread weiterarbeiten kann. Zum Beispiel, weil man einen Spinlock implementieren will.
Nun es gibt insgesamt vier Methoden die durch das Netz geistern.
Ich gehe mal der Häufigkeit nach, die so in manchen Code-Samples finde:
1. __noop;
Wenn der Lock kurz ist, scheint es das beste zu sein, einfach die Schleife weiterlaufen zu lassen und zu hoffen, dass der ein Thread auf einem anderen Prozessor, die Ressource freigibt. Das eignet sich wirklich nur, wenn die Zeitdauer der Sperre als extrem kurz anzusehen ist und eine hohe Anzahl von Prozessoren zur Verfügung steht.
Nach allen Test, die ich gemacht habe, sollte man aber von dieser Art des Wartens bei einem Spinlock absehen. Es schiebt die Leistung des Kerns auf 100% und bringt nichts.
2. Sleep(0);
Lies sich gut. Schlafe aber eben nicht lange. Man hat auch schon irgendwo gelesen, dass durch diese Methode der Rest der Zeitscheibe dieses Threads aufgegeben wird und ein anderer Thread an die Reihe kommt.
Leider stimmt das nicht ganz ❗
Liest man die Doku genau steht da aber:
A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution.
😮 Threads mit höherer oder niedriger Prio haben also nichts davon.
Besonders eklig wird das ganze gerade wenn man Threads unterschiedlicher Prio hat, die hier gegeneinander laufen. Sleep(0); führt in diesem Fall zu einerunnötigen Prozessorlast und eben nicht dazu, dass die Zeitscheibe abgegeben wird. Der Prozess kommt sofort wieder an die Reihe und spin-t weiter.
3. SwitchToThread();
OK. Seit Windows 2000 gibt es diese nette neue Funktion. Damit wird ein anderer Thread aktiv. Egal was für eine Prio er hat. Aber auch diese Funktion tut evtl. nicht genau das was man will.
Auch hier stecken die Tücken im Detail der Doku:
The yield of execution is limited to the processor of the calling thread. The operating system will not switch execution to another processor, even if that processor is idle or is running a thread of lower priority.
Sollte also der Thread, auf den man wartet auf dem anderen Prozessor laufen, so profitiert der nicht von dem Aufruf von SwitchToThread.
4. Sleep(1):
Hiermit erreicht man wirklich was man möchte. Man gibt seine Timeslice auf und erstmal sind die anderen dran.
Mein persönliches Fazit:
Nach meinen Recherchen ist Sleep(1); der vernünftigste Weg seine Zeitscheibe abzugeben. Und nach meinem Dafürhalten ist ein __noop; strickt zu vermeiden. Die Performance ist grottenschlecht.
Das ganze Verhalten hängt extrem auch von den Umständen ab: Zahl der Theads, Häufigkeit der Kollision, Anzahl verfügbare Prozessoren, Verteilung der Prioritäten, Allgemeine Belastung des Systems, Zeitdauer der Sperre etc.
Ich habe mit einigen Parametern gespielt und auch ein kleines Sample gebaut, dass alle 4 oben genannten Funktionen durchprobiert und in dem man auch mit anderen Faktoren (Priorität etc.) spielen kann.
Es zeigte sich, dass Sleep(1); am effektivsten war. Aber dicht auf gefolgt von Sleep(0);, was mich doch etwas überraschte.
Allerdings führen schon kleinste Änderungen (Lockdauer, Zahl der Prozessoren, Spielen mit der Priorität) zu anderen Ergebnissen.
Interessant ist vor allem das Spielen mit den Prioritäten. Man soll nicht glauben, das ein Thread selbst mit niedrigster Prio noch relativ häufig Arbeit bekommt.
Viel Spaß beim Spielen mit dem Code SleepTest