Wann sollte man einen Spinlock anstelle von Mutex verwenden?

294

Ich denke, beide machen den gleichen Job. Wie entscheiden Sie, welchen Sie für die Synchronisation verwenden möchten?

Compile-Fan
quelle
1
mögliches Duplikat von Spinlock Versus Semaphore!
Paul R
11
Mutex und Semaphore sind nicht dasselbe, daher denke ich nicht, dass dies ein Duplikat ist. Die Antwort des Artikels, auf den verwiesen wird, gibt dies korrekt an. Weitere Informationen finden Sie unter barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore
Nanoquack

Antworten:

729

Die Theorie

Wenn ein Thread versucht, einen Mutex zu sperren, und dies nicht erfolgreich ist, weil der Mutex bereits gesperrt ist, wird er theoretisch in den Ruhezustand versetzt, sodass sofort ein anderer Thread ausgeführt werden kann. Es wird weiter schlafen, bis es aufgeweckt wird. Dies ist der Fall, sobald der Mutex von einem Thread entsperrt wird, der zuvor die Sperre gehalten hat. Wenn ein Thread versucht, einen Spinlock zu sperren, und dies nicht erfolgreich ist, versucht er kontinuierlich, ihn zu sperren, bis er schließlich erfolgreich ist. Daher kann kein anderer Thread seinen Platz einnehmen (das Betriebssystem wechselt jedoch zwangsweise zu einem anderen Thread, sobald das CPU-Laufzeitquantum des aktuellen Threads natürlich überschritten wurde).

Das Problem

Das Problem bei Mutexen ist, dass das Einschalten und erneutes Aufwecken von Threads ziemlich teure Vorgänge sind. Sie benötigen eine Menge CPU-Anweisungen und benötigen daher auch einige Zeit. Wenn der Mutex jetzt nur für eine sehr kurze Zeit gesperrt war, kann die Zeit, die zum Einschalten und erneuten Aufwecken eines Threads aufgewendet wird, die Zeit, in der der Thread tatsächlich geschlafen hat, bei weitem überschreiten und sogar die Zeit überschreiten, die der Thread benötigt haben durch ständige Abfrage auf einem Spinlock verschwendet. Auf der anderen Seite verschwendet das Abrufen eines Spinlocks ständig CPU-Zeit. Wenn die Sperre länger gehalten wird, verschwendet dies viel mehr CPU-Zeit und es wäre viel besser gewesen, wenn der Thread stattdessen geschlafen hätte.

Die Lösung

Die Verwendung von Spinlocks auf einem Single-Core- / Single-CPU-System ist normalerweise nicht sinnvoll, da kein anderer Thread ausgeführt werden kann, solange die Spinlock-Abfrage den einzigen verfügbaren CPU-Core blockiert, und die Sperre nicht ausgeführt wird, da kein anderer Thread ausgeführt werden kann entweder entsperrt werden. IOW, ein Spinlock verschwendet auf diesen Systemen nur CPU-Zeit ohne wirklichen Nutzen. Wenn der Thread stattdessen in den Ruhezustand versetzt wurde, könnte ein anderer Thread sofort ausgeführt worden sein, wodurch möglicherweise die Sperre aufgehoben und der erste Thread die Verarbeitung fortsetzen konnte, sobald er wieder aufwachte.

Auf einem Multi-Core- / Multi-CPU-System mit vielen Sperren, die nur für eine sehr kurze Zeit gehalten werden, kann die Zeit, die verschwendet wird, um Threads ständig in den Ruhezustand zu versetzen und sie wieder aufzuwecken, die Laufzeitleistung merklich verringern. Wenn Threads stattdessen Spinlocks verwenden, können Threads ihr volles Laufzeitquantum nutzen (immer nur für einen sehr kurzen Zeitraum blockieren, dann aber sofort ihre Arbeit fortsetzen), was zu einem viel höheren Verarbeitungsdurchsatz führt.

Die Übung

Da Programmierer sehr oft nicht im Voraus wissen können, ob Mutexe oder Spinlocks besser sind (z. B. weil die Anzahl der CPU-Kerne der Zielarchitektur unbekannt ist), können Betriebssysteme auch nicht wissen, ob ein bestimmter Code für Single-Core oder optimiert wurde In Multi-Core-Umgebungen unterscheiden die meisten Systeme nicht streng zwischen Mutexen und Spinlocks. Tatsächlich verfügen die meisten modernen Betriebssysteme über Hybrid-Mutexe und Hybrid-Spinlocks. Was bedeutet das eigentlich?

Ein Hybrid-Mutex verhält sich auf einem Mehrkernsystem zunächst wie ein Spinlock. Wenn ein Thread den Mutex nicht sperren kann, wird er nicht sofort in den Ruhezustand versetzt, da der Mutex möglicherweise bald entsperrt wird. Stattdessen verhält sich der Mutex zunächst genau wie ein Spinlock. Nur wenn die Sperre nach einer bestimmten Zeit (oder Wiederholungsversuchen oder einem anderen Messfaktor) immer noch nicht erreicht wurde, wird der Thread wirklich in den Ruhezustand versetzt. Wenn derselbe Code auf einem System mit nur einem Kern ausgeführt wird, wird der Mutex nicht spinlockiert, obwohl dies, wie oben gezeigt, nicht vorteilhaft wäre.

Ein Hybrid-Spinlock verhält sich zunächst wie ein normaler Spinlock. Um jedoch zu vermeiden, dass zu viel CPU-Zeit verschwendet wird, kann eine Back-Off-Strategie angewendet werden. Normalerweise wird der Thread nicht in den Ruhezustand versetzt (da dies bei Verwendung eines Spinlocks nicht geschehen soll), es kann jedoch sein, dass der Thread gestoppt wird (entweder sofort oder nach einer bestimmten Zeit) und ein anderer Thread ausgeführt werden kann Dies erhöht die Wahrscheinlichkeit, dass der Spinlock entsperrt wird (ein reiner Thread-Schalter ist normalerweise günstiger als einer, bei dem ein Thread in den Ruhezustand versetzt und später wieder aufgeweckt wird, wenn auch nicht bei weitem).

Zusammenfassung

Wenn Sie Zweifel haben, verwenden Sie Mutexe. Diese sind normalerweise die bessere Wahl, und die meisten modernen Systeme ermöglichen es ihnen, für sehr kurze Zeit einen Spinlock durchzuführen, wenn dies vorteilhaft erscheint. Die Verwendung von Spinlocks kann manchmal die Leistung verbessern, aber nur unter bestimmten Bedingungen und die Tatsache, dass Sie Zweifel haben, sagt mir eher, dass Sie derzeit an keinem Projekt arbeiten, bei dem ein Spinlock von Vorteil sein könnte. Sie könnten in Betracht ziehen, Ihr eigenes "Sperrobjekt" zu verwenden, das entweder intern einen Spinlock oder einen Mutex verwenden kann (z. B. könnte dieses Verhalten beim Erstellen eines solchen Objekts konfigurierbar sein), zunächst überall Mutexe zu verwenden und wenn Sie der Meinung sind, dass die Verwendung eines Spinlocks irgendwo wirklich möglich ist Hilfe, probieren Sie es aus und vergleichen Sie die Ergebnisse (z. B. mit einem Profiler), aber testen Sie beide Fälle.

Update: Eine Warnung für iOS

Eigentlich nicht iOS-spezifisch, aber iOS ist die Plattform, auf der die meisten Entwickler möglicherweise mit diesem Problem konfrontiert sind: Wenn Ihr System über einen Thread-Scheduler verfügt, garantiert dies nicht, dass ein Thread, egal wie niedrig seine Priorität sein mag, irgendwann eine Chance zum Ausführen erhält. dann können Spinlocks zu dauerhaften Deadlocks führen. Der iOS-Scheduler unterscheidet verschiedene Klassen von Threads, und Threads in einer niedrigeren Klasse werden nur ausgeführt, wenn kein Thread in einer höheren Klasse ebenfalls ausgeführt werden soll. Hierfür gibt es keine Back-Off-Strategie. Wenn also permanent hochklassige Threads verfügbar sind, erhalten niedrigklassige Threads niemals CPU-Zeit und somit keine Chance, Arbeiten auszuführen.

Das Problem sieht wie folgt aus: Ihr Code erhält einen Spinlock in einem Thread mit niedriger Prio-Klasse, und während er sich in der Mitte dieser Sperre befindet, hat das Zeitquantum überschritten und der Thread wird nicht mehr ausgeführt. Die einzige Möglichkeit, wie dieser Spinlock wieder freigegeben werden kann, besteht darin, dass dieser Thread mit niedriger Prio-Klasse wieder CPU-Zeit erhält, dies jedoch nicht garantiert ist. Möglicherweise haben Sie einige Threads der High-Prio-Klasse, die ständig ausgeführt werden sollen, und der Taskplaner priorisiert diese immer. Einer von ihnen kann über das Spinlock laufen und versuchen, es zu erhalten, was natürlich nicht möglich ist, und das System wird es nachgeben lassen. Das Problem ist: Ein Thread, der nachgab, kann sofort wieder ausgeführt werden! Mit einem höheren Prio als der Thread, der die Sperre hält, hat der Thread, der die Sperre hält, keine Chance, die CPU-Laufzeit zu erhalten.

Warum tritt dieses Problem bei Mutexen nicht auf? Wenn der High-Prio-Thread den Mutex nicht erhalten kann, gibt er nicht nach, dreht sich möglicherweise ein wenig, wird aber schließlich in den Ruhezustand versetzt. Ein schlafender Thread kann erst ausgeführt werden, wenn er von einem Ereignis geweckt wurde, z. B. einem Ereignis wie dem entsperrten Mutex, auf das er gewartet hat. Apple ist sich dieses Problems bewusst und ist daher veraltet OSSpinLock. Das neue Schloss wird aufgerufen os_unfair_lock. Diese Sperre vermeidet die oben erwähnte Situation, da sie die verschiedenen Thread-Prioritätsklassen kennt. Wenn Sie sicher sind, dass die Verwendung von Spinlocks in Ihrem iOS-Projekt eine gute Idee ist, verwenden Sie diese. Bleiben Sie weg vonOSSpinLock! Und implementieren Sie unter keinen Umständen Ihre eigenen Spinlocks in iOS! Verwenden Sie im Zweifelsfall einen Mutex! macOS ist von diesem Problem nicht betroffen, da es einen anderen Thread-Scheduler hat, der es keinem Thread (auch Threads mit niedrigem Prio) erlaubt, während der CPU-Zeit "trocken zu laufen". Dennoch kann dort die gleiche Situation auftreten und führt dann zu einer sehr schlechten Situation Leistung ist daher auch OSSpinLockunter macOS veraltet.

Mecki
quelle
3
Hervorragende Erklärung ... Ich habe Zweifel an Spinlock. Kann ich einen Spinlock in ISR verwenden? wenn nein warum nicht
haris
4
@Mecki Wenn ich mich nicht irre, haben Sie in Ihrer Antwort meiner Meinung nach vorgeschlagen, dass Time Slicing nur auf Einzelprozessorsystemen auftritt. Das ist nicht richtig! Sie können eine Spin-Sperre für ein Einzelprozessorsystem verwenden, die sich dreht, bis das Zeitquantum abläuft. Dann kann ein anderer Thread mit der gleichen Priorität übernehmen (genau wie Sie es für Multiprozessorsysteme beschrieben haben).
Fumoboy007
7
@ fumoboy007 "und es dreht sich, bis sein Zeitquantum abläuft" // Was bedeutet, dass Sie CPU-Zeit / Batterieleistung für absolut nichts ohne einen einzigen Vorteil verschwenden, was absolut schwachsinnig ist. Und nein, ich habe nirgends gesagt, dass Time Slicing nur auf Single-Core-Systemen stattfindet. Ich habe gesagt, dass es auf Single-Core-Systemen NUR Time Slicing gibt, während es ECHTE Parallelität zwischen Multicore-Systemen gibt (und auch Time Slicing, aber irrelevant für das, was ich in meinem geschrieben habe Antworten); Außerdem haben Sie völlig übersehen, was ein Hybrid-Spinlock ist und warum er auf Single- und Multicore-Systemen gut funktioniert.
Mecki
11
@ fumoboy007 Thread A hält die Sperre und ist unterbrochen. Thread B läuft und will das Schloss, kann es aber nicht bekommen, also dreht es sich. Auf einem Multicore-System kann Thread A weiterhin auf einem anderen Kern ausgeführt werden, während sich Thread B noch dreht, die Sperre aufheben und Thread B kann innerhalb seines aktuellen Zeitquantums fortgesetzt werden. Auf einem Single-Core-System kann nur ein Core-Thread A ausgeführt werden, um die Sperre aufzuheben, und dieser Core wird durch das Drehen von Thread B beschäftigt. Es gibt also keine Möglichkeit, den Spinlock jemals freizugeben, bevor Thread B sein Zeitquantum überschritten hat, und daher ist alles Spinnen nur Zeitverschwendung.
Mecki
2
Wenn Sie mehr über Spinlocks und Mutexe erfahren möchten, die im Linux-Kernel implementiert sind, empfehle ich dringend, Kapitel 5 der großartigen Linux-Gerätetreiber, Third Edition (LDD3) (Mutexe: Seite 109; Spinlocks: Seite 116) zu lesen .
patryk.beza
7

In Fortsetzung von Meckis Vorschlag zeigt dieser Artikel pthread mutex vs pthread spinlock auf Alexander Sandlers Blog, Alex unter Linux, wie das spinlock& mutexesimplementiert werden kann, um das Verhalten mit #ifdef zu testen.

Stellen Sie jedoch sicher, dass Sie den letzten Anruf aufgrund Ihrer Beobachtung entgegennehmen. Da das angegebene Beispiel ein Einzelfall ist, kann Ihre Projektanforderung und die Umgebung völlig anders sein.

TheCottonSilk
quelle
6

Bitte beachten Sie auch, dass Sie in bestimmten Umgebungen und Bedingungen (z. B. unter Windows auf Versandebene> = DISPATCH LEVEL) keinen Mutex, sondern Spinlock verwenden können. Unter Unix - das Gleiche.

Hier ist eine gleichwertige Frage auf der Stackexchange-Unix-Site eines Mitbewerbers: /unix/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-something- Mehr

Informationen zum Versand auf Windows-Systemen: http://download.microsoft.com/download/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc

Dan Jobs
quelle
6

Meckis Antwort trifft es ziemlich gut. Auf einem einzelnen Prozessor kann die Verwendung eines Spinlocks jedoch sinnvoll sein, wenn die Aufgabe auf die Sperre wartet, die von einer Interrupt-Serviceroutine gegeben werden soll. Der Interrupt würde die Steuerung an den ISR übertragen, der die Ressource für die Verwendung durch die wartende Aufgabe bereit machen würde. Zum Schluss wird die Sperre aufgehoben, bevor die unterbrochene Aufgabe wieder kontrolliert wird. Die Spinnaufgabe würde den verfügbaren Spinlock finden und fortfahren.

AlanC
quelle
2
Ich bin mir nicht sicher, ob ich dieser Antwort vollständig zustimmen kann. Wenn ein einzelner Prozessor eine Ressource sperrt, kann der ISR nicht sicher fortfahren und nicht darauf warten, dass die Task die Ressource entsperrt (da die Task, die die Ressource enthält, unterbrochen wurde). In solchen Fällen sollte die Aufgabe einfach Interrupts deaktivieren, um den Ausschluss zwischen sich und dem ISR zu erzwingen. Dies müsste natürlich für sehr kurze Zeitintervalle erfolgen.
user1202136
3

Spinlock- und Mutex-Synchronisationsmechanismen sind heutzutage weit verbreitet.

Denken wir zuerst an Spinlock.

Grundsätzlich handelt es sich um eine ausgelastete Warteaktion, was bedeutet, dass wir warten müssen, bis eine bestimmte Sperre aufgehoben wird, bevor wir mit der nächsten Aktion fortfahren können. Konzeptionell sehr einfach, während die Implementierung nicht der Fall ist. Zum Beispiel: Wenn die Sperre nicht freigegeben wurde, wurde der Thread ausgetauscht und in den Ruhezustand versetzt. Sollten wir uns damit befassen? Wie gehe ich mit Synchronisationssperren um, wenn zwei Threads gleichzeitig Zugriff anfordern?

Im Allgemeinen ist die intuitivste Idee die Synchronisation über eine Variable, um den kritischen Abschnitt zu schützen. Das Konzept von Mutex ist ähnlich, aber sie unterscheiden sich immer noch. Fokus auf: CPU-Auslastung. Spinlock benötigt CPU-Zeit, um auf die Ausführung der Aktion zu warten. Daher können wir den Unterschied zwischen beiden zusammenfassen:

Wenn in homogenen Multi-Core-Umgebungen der Zeitaufwand für kritische Abschnitte gering ist, verwenden Sie Spinlock, da wir die Zeit für den Kontextwechsel reduzieren können. (Single-Core-Vergleich ist nicht wichtig, da einige Systeme Spinlock in der Mitte des Switches implementieren)

Unter Windows wird durch die Verwendung von Spinlock der Thread auf DISPATCH_LEVEL aktualisiert, was in einigen Fällen möglicherweise nicht zulässig ist. Daher mussten wir diesmal einen Mutex (APC_LEVEL) verwenden.

Marcus Thornton
quelle
-6

Die Verwendung von Spinlocks auf einem Single-Core- / Single-CPU-System ist normalerweise nicht sinnvoll, da kein anderer Thread ausgeführt werden kann, solange die Spinlock-Abfrage den einzigen verfügbaren CPU-Core blockiert, und die Sperre nicht ausgeführt wird, da kein anderer Thread ausgeführt werden kann entweder entsperrt werden. IOW, ein Spinlock verschwendet auf diesen Systemen nur CPU-Zeit ohne wirklichen Nutzen

Das ist falsch. Bei der Verwendung von Spinlocks auf Uni-Prozessorsystemen werden keine CPU-Zyklen verschwendet, da die Preemption deaktiviert wird, sobald ein Prozess eine Spin-Sperre durchführt. Daher kann sich niemand anders drehen! Es macht nur keinen Sinn, es zu benutzen! Daher werden Spinlocks auf Uni-Systemen beim Kompilieren durch preempt_disable durch den Kernel ersetzt!

Neelansh Mittal
quelle
Das Zitierte ist immer noch völlig wahr. Wenn das kompilierte Ergebnis des Quellcodes keine Spinlocks enthält, ist das Zitat irrelevant. Angenommen, das, was Sie gesagt haben, trifft zu, dass der Kernel Spinlocks zur Kompilierungszeit ersetzt. Wie werden Spinlocks behandelt, wenn sie auf einem anderen Computer vorkompiliert werden, der möglicherweise ein Uniprozessor ist oder nicht, es sei denn, es handelt sich ausschließlich um Spinlocks nur im Kernel selbst?
Hydranix
"Sobald ein Prozess eine Spin-Sperre durchführt, ist die Vorauszahlung deaktiviert." Die Vorauszahlung ist nicht deaktiviert, wenn ein Prozess ausgeführt wird. Wenn dies der Fall wäre, könnte ein einzelner Prozess die gesamte Maschine ausschalten, indem nur ein Spinlock eingegeben und niemals verlassen wird. Beachten Sie, dass, wenn Ihr Thread im Kernel-Space (anstelle des User-Space) ausgeführt wird, das Aktivieren einer Spin-Sperre zwar die Preemption deaktiviert, aber ich denke nicht, dass dies hier diskutiert wird.
Konstantin Weitz
Zur Kompilierungszeit durch den Kernel ?
Shien
@konstantin FYI Spin Lock kann nur im Kernelraum verwendet werden. Und wenn eine Spin-Sperre aktiviert ist, ist die Preemption auf dem lokalen Prozessor deaktiviert.
Neelansh Mittal
@hydranix Haben Sie nicht verstanden? Offensichtlich können Sie kein Modul für einen Kernel kompilieren, in dem CONFIG_SMP aktiviert ist, und dasselbe Modul auf einem Kernel ausführen, für den CONFIG_SMP deaktiviert ist.
Neelansh Mittal