Warum gibt es in der internen .NET-Hashtabelle einen Thread.Sleep (1)?

70

Kürzlich habe ich die Implementierung von .NET Hashtable gelesen und bin auf Code gestoßen, den ich nicht verstehe. Ein Teil des Codes ist:

int num3 = 0;
int num4;
do
{
   num4 = this.version;
   bucket = bucketArray[index];
   if (++num3 % 8 == 0)
     Thread.Sleep(1);
}
while (this.isWriterInProgress || num4 != this.version);

Der gesamte Code befindet sich innerhalb public virtual object this[object key]von System.Collections.Hashtable(mscorlib Version = 4.0.0.0).

Die Frage ist:

Was ist der Grund, Thread.Sleep(1)dort zu sein?

Dariusz Woźniak
quelle
7
Sieht aus wie ein Spinwait vor einem Kontextwechsel. Dh es prüft 8 Mal auf einen Zustand, bevor der Thread in den Ruhezustand versetzt wird.
Groo
4
Ähnlicher Thread ; Crux soll OS erlauben, andere Aufgaben zu planen
Konstantin

Antworten:

70

Sleep (1) ist eine dokumentierte Methode in Windows, um den Prozessor bereitzustellen und die Ausführung anderer Threads zu ermöglichen. Sie finden diesen Code in der Referenzquelle mit Kommentaren:

   // Our memory model guarantee if we pick up the change in bucket from another processor,
   // we will see the 'isWriterProgress' flag to be true or 'version' is changed in the reader.
   //
   int spinCount = 0;
   do {
       // this is violate read, following memory accesses can not be moved ahead of it.
       currentversion = version;
       b = lbuckets[bucketNumber];

       // The contention between reader and writer shouldn't happen frequently.
       // But just in case this will burn CPU, yield the control of CPU if we spinned a few times.
       // 8 is just a random number I pick.
       if( (++spinCount) % 8 == 0 ) {
           Thread.Sleep(1);   // 1 means we are yeilding control to all threads, including low-priority ones.
       }
   } while ( isWriterInProgress || (currentversion != version) );

Die Variable isWriterInProgress ist ein flüchtiger Bool. Der Autor hatte einige Probleme mit Englisch "Lesen verletzen" ist "flüchtiges Lesen". Die Grundidee ist, das Nachgeben zu vermeiden. Thread-Kontextwechsel sind sehr teuer, mit der Hoffnung, dass der Writer schnell fertig wird. Wenn dies nicht funktioniert, geben Sie explizit nach, um das Verbrennen der CPU zu vermeiden. Dies wäre wahrscheinlich heute mit Spinlock geschrieben worden, aber Hashtable ist sehr alt. Wie sind die Annahmen über das Speichermodell.

Hans Passant
quelle
8
Ich dachte, Sleep(0)wurde empfohlen, wenn Sie nur eine Rendite wollen. Sleep(1)ist eigentlich ein Schlaf.
Ben Voigt
11
Sleep (0) ergibt nur, wenn ein anderer Thread mit höherer Priorität ausgeführt werden kann. Nicht die Absicht hier.
Hans Passant
Eigentlich nur für Threads gleicher Priorität ... was Ausbeute im Allgemeinen bedeutet.
Ben Voigt
4
Gleich oder höher. Das Schreiben dauert sehr wenig. Dies bedeutet, dass der Schreibthread angehalten worden sein muss, wenn dies nach 8 Versuchen nicht funktioniert. Dies ist kein großartiger Code.
Hans Passant
Wenn ein Thread mit höherer Priorität zur Ausführung bereit wäre, würde er bereits ausgeführt. Wenn Ihr Code ausgeführt wird, werden alle Threads mit höherer Priorität entweder ebenfalls ausgeführt (auf anderen Kernen) oder blockiert. Schlaf (0) ergibt also nur die gleiche Priorität. Natürlich unter Berücksichtigung der dynamischen Priorität.
Ben Voigt
7

Ohne Zugriff auf den Rest des Implementierungscodes kann ich nur anhand Ihrer Beiträge eine fundierte Vermutung anstellen.

Das heißt, es sieht so aus, als würde versucht, etwas in der Hashtabelle zu aktualisieren, entweder im Speicher oder auf der Festplatte, und eine Endlosschleife zu machen, während darauf gewartet wird, dass es beendet wird (wie durch Überprüfen der isWriterInProgress).

Wenn es sich um einen Single-Core-Prozessor handelt, kann jeweils nur ein Thread ausgeführt werden. Eine solche Endlosschleife kann leicht bedeuten, dass der andere Thread keine Chance hat, ausgeführt zu werden, aber dies Thread.Sleep(1)gibt dem Prozessor die Möglichkeit, dem Writer Zeit zu geben. Ohne das Warten erhält der Writer-Thread möglicherweise nie die Chance, ausgeführt zu werden, und wird niemals abgeschlossen.

Tarka
quelle
5

Ich habe die Quelle nicht gelesen, aber es sieht aus wie eine Sache mit sperrenloser Parallelität. Sie versuchen, aus der Hashtabelle zu lesen, aber möglicherweise schreibt eine andere Person darauf. Warten Sie also, bis sie nicht mehr festgelegt isWriterInProgressist und sich die von Ihnen gelesene Version nicht geändert hat.

Dies erklärt nicht, warum wir zB immer mindestens einmal warten. EDIT: Das liegt daran, dass wir es nicht tun. Vielen Dank an @Maciej für den Hinweis. Wenn es keinen Streit gibt, fahren wir sofort fort. Ich weiß jedoch nicht, warum 8 die magische Zahl ist, anstatt zB 4 oder 16.

David Seiler
quelle
3
Nein, tun wir nicht. Nach ++num3, num3wird 1. Oder fehlt mir ernsthaft Koffein.
Maciej Stachowski
Der Schlaf wird erst nach 8 aufeinanderfolgenden Iterationen durchgeführt, in denen ein Writer ausgeführt wird oder die Version falsch ist. Vermutlich, um dem Autoren-Thread eine Chance zu geben, seine Arbeit zu erledigen.
Dan04
3
Wenn wir bei jedem Aufruf des Operators [] Sleep (1) aufrufen würden, wäre dies ernsthaft schlecht für die Leistung. Denken Sie daran, dass Thread.Sleep (1) Sie nicht wirklich 1 ms schlafen lässt - es ist eher wie 15.
Maciej Stachowski
Und was hat num4 damit zu tun?
BlackBear
2
@BlackBear - es hat mich zuerst auch umgehauen, aber wenn Sie erkennen, dass versionwahrscheinlich die Version der Daten in der Hashtabelle bedeutet (dh jeder Schreibvorgang erstellt eine neue), macht es Sinn :)
Maciej Stachowski