Ein Spin-Lock ist eine Möglichkeit, eine gemeinsam genutzte Ressource davor zu schützen, von zwei oder mehr Prozessen gleichzeitig geändert zu werden. Der erste Prozess, der versucht, die Ressource zu ändern, "erwirbt" die Sperre und setzt seinen Weg fort, indem er mit der Ressource tut, was er tun sollte. Alle anderen Prozesse, die anschließend versuchen, die Sperre zu erlangen, werden gestoppt; man sagt, dass sie "an Ort und Stelle drehen" und darauf warten, dass die Sperre durch den ersten Prozess freigegeben wird, daher der Name Drehsperre.
Der Linux-Kernel verwendet Spinlocks für viele Dinge, beispielsweise beim Senden von Daten an ein bestimmtes Peripheriegerät. Die meisten Hardware-Peripheriegeräte sind nicht darauf ausgelegt, mehrere gleichzeitige Zustandsaktualisierungen zu verarbeiten. Wenn zwei verschiedene Modifikationen stattfinden müssen, muss eine strikt auf die andere folgen, sie dürfen sich nicht überschneiden. Eine Drehsperre bietet den notwendigen Schutz und stellt sicher, dass die Änderungen nacheinander erfolgen.
Spin-Locks sind ein Problem, da das Spinning den CPU-Kern dieses Threads daran hindert, andere Aufgaben zu erledigen. Während der Linux-Kernel Multitasking-Dienste für darunter laufende User-Space-Programme bereitstellt, erstreckt sich diese Allzweck-Multitasking-Funktion nicht auf Kernel-Code.
Diese Situation ändert sich und das schon seit Linux. Bis Linux 2.0 war der Kernel fast ausschließlich ein Single-Tasking-Programm:Immer wenn die CPU Kernel-Code ausführte, wurde nur ein CPU-Kern verwendet, da es eine einzige Spin-Sperre gab, die alle gemeinsam genutzten Ressourcen schützte, genannt Big Kernel Lock (BKL ). Beginnend mit Linux 2.2 wird die BKL langsam in viele unabhängige Sperren aufgeteilt, die jeweils eine stärker fokussierte Klasse von Ressourcen schützen. Heute, mit Kernel 2.6, existiert die BKL immer noch, aber sie wird nur von wirklich altem Code verwendet, der nicht ohne weiteres in eine granularere Sperre verschoben werden kann. Es ist jetzt für eine Multicore-Box durchaus möglich, dass jede CPU nützlichen Kernel-Code ausführt.
Der Nutzen des Aufbrechens der BKL ist begrenzt, da dem Linux-Kernel allgemeines Multitasking fehlt. Wenn ein CPU-Kern bei einer Kernel-Spin-Sperre blockiert wird, kann er nicht erneut beauftragt werden, etwas anderes zu tun, bis die Sperre aufgehoben wird. Es sitzt einfach und dreht sich, bis die Sperre gelöst wird.
Spinlocks können eine Monster-16-Core-Box effektiv in eine Single-Core-Box verwandeln, wenn die Arbeitslast so hoch ist, dass jeder Kern immer auf einen einzelnen Spinlock wartet. Dies ist die Hauptgrenze für die Skalierbarkeit des Linux-Kernels:Das Verdoppeln der CPU-Kerne von 2 auf 4 wird wahrscheinlich die Geschwindigkeit einer Linux-Box fast verdoppeln, aber das Verdoppeln von 16 auf 32 wird es bei den meisten Workloads wahrscheinlich nicht tun.
Eine Spin-Sperre liegt vor, wenn ein Prozess kontinuierlich abfragt, ob eine Sperre entfernt werden soll. Es wird als schlecht angesehen, da der Prozess (normalerweise) unnötig Zyklen verbraucht. Es ist nicht Linux-spezifisch, sondern ein allgemeines Programmiermuster. Und obwohl es allgemein als schlechte Praxis gilt, ist es tatsächlich die richtige Lösung; Es gibt Fälle, in denen die Kosten für die Verwendung des Schedulers (in Bezug auf CPU-Zyklen) höher sind als die Kosten für die wenigen Zyklen, die der Spinlock voraussichtlich dauern wird.
Beispiel für ein Spinlock:
#!/bin/sh
#wait for some program to clear a lock before doing stuff
while [ -f /var/run/example.lock ]; do
sleep 1
done
#do stuff
Häufig gibt es eine Möglichkeit, ein Spinlock zu vermeiden. Für dieses spezielle Beispiel gibt es ein Linux-Tool namens inotifywait (es wird normalerweise nicht standardmäßig installiert). Wenn es in C geschrieben wäre, würden Sie einfach die Inotify-API verwenden, die Linux bereitstellt.
Dasselbe Beispiel mit inotifywait zeigt, wie man dasselbe ohne Spin-Lock erreicht:
#/bin/sh
inotifywait -e delete_self /var/run/example.lock
#do stuff
Wenn ein Thread versucht, eine Sperre zu erlangen, können drei Dinge passieren, wenn dies fehlschlägt – er kann versuchen und blockieren, er kann versuchen und fortfahren, er kann versuchen, dann in den Ruhezustand zu wechseln und dem Betriebssystem mitteilen, dass es ihn aufwecken soll, wenn ein Ereignis eintritt.
Jetzt verbraucht ein Try and Continue viel weniger Zeit als ein Try and Block. Nehmen wir für den Moment an, dass ein "Try and Continue" eine Zeiteinheit und ein "Try and Block" hundert Zeiteinheiten benötigt.
Nehmen wir für den Moment an, dass ein Thread im Durchschnitt 4 Zeiteinheiten benötigt, um die Sperre zu halten. Es ist verschwenderisch, 100 Einheiten zu warten. Also schreiben Sie stattdessen eine Schleife von "Try and Continues". Beim vierten Versuch erhalten Sie normalerweise die Sperre. Dies ist ein Spin-Lock. Es wird so genannt, weil der Thread an Ort und Stelle rotiert, bis er den Lock bekommt.
Eine zusätzliche Sicherheitsmaßnahme besteht darin, die Anzahl der Wiederholungen der Schleife zu begrenzen. So führen Sie zum Beispiel eine for-Schleife aus, sagen wir, sechsmal, wenn sie fehlschlägt, dann "versuchen und blockieren".
Wenn Sie wissen, dass ein Thread die Sperre immer für, sagen wir, 200 Einheiten halten wird, dann verschwenden Sie die Computerzeit für jeden Versuch und fahren fort.
Letztendlich kann ein Spinlock also sehr effizient oder verschwenderisch sein. Es ist verschwenderisch, wenn die "typische" Zeit zum Halten einer Sperre länger ist als die Zeit, die zum "Versuchen und Blockieren" benötigt wird. Es ist effizient, wenn die typische Zeit zum Halten einer Sperre viel kürzer ist als die Zeit zum "Versuchen und Blockieren".
Ps:Das Buch zum Thema Threads ist "A Thread Primer", falls du es noch finden kannst.