Ich denke, dass beide die gleiche Aufgabe erfüllen. Wie entscheiden Sie, welches Programm Sie für die Synchronisierung verwenden?
Antworten
Zu viele Anzeigen?Die Theorie
Wenn ein Thread versucht, einen Mutex zu sperren und dies nicht gelingt, weil der Mutex bereits gesperrt ist, geht er theoretisch in den Ruhezustand über, so dass ein anderer Thread sofort starten kann. Er schläft weiter, bis er aufgeweckt wird, was der Fall ist, wenn der Mutex von dem Thread, der die Sperre zuvor gehalten hat, entsperrt wird. Wenn ein Thread versucht, einen Spinlock zu sperren, und dies nicht gelingt, wird er immer wieder versuchen, ihn zu sperren, bis es ihm schließlich gelingt; er lässt also nicht zu, dass ein anderer Thread seinen Platz einnimmt (das Betriebssystem schaltet jedoch zwangsweise auf einen anderen Thread um, sobald das CPU-Laufzeitquantum des aktuellen Threads überschritten wurde).
Das Problem
Das Problem mit Mutexen ist, dass das Einschlafen und Aufwecken von Threads beides ziemlich teure Operationen sind, die ziemlich viele CPU-Befehle benötigen und daher auch einige Zeit in Anspruch nehmen. Wenn nun der Mutex nur für eine sehr kurze Zeit gesperrt war, könnte die Zeit, die für das Einschlafen und Aufwecken eines Threads aufgewendet wird, die Zeit, die der Thread tatsächlich geschlafen hat, bei weitem übersteigen, und sie könnte sogar die Zeit übersteigen, die der Thread durch das ständige Abfragen eines Spinlocks verschwendet hätte. Andererseits wird durch die ständige Abfrage eines Spinlocks ständig CPU-Zeit verschwendet, und wenn die Sperre über einen längeren Zeitraum gehalten wird, wird dadurch noch viel mehr CPU-Zeit verschwendet, und es wäre viel besser gewesen, wenn der Thread stattdessen schlafen würde.
Die Lösung
Die Verwendung von Spinlocks auf einem Single-Core-/Single-CPU-System macht in der Regel keinen Sinn, denn solange die Spinlock-Abfrage den einzigen verfügbaren CPU-Kern blockiert, kann kein anderer Thread ausgeführt werden, und da kein anderer Thread ausgeführt werden kann, wird die Sperre auch nicht entsperrt werden. D.h., ein Spinlock verschwendet auf diesen Systemen nur CPU-Zeit ohne wirklichen Nutzen. Wäre der Thread stattdessen in den Ruhezustand versetzt worden, hätte ein anderer Thread sofort ausgeführt werden können, wodurch die Sperre möglicherweise aufgehoben worden wäre und der erste Thread die Verarbeitung hätte fortsetzen können, sobald er wieder aufgewacht wäre.
Auf einem Multi-Core-/Multi-CPU-System mit vielen Sperren, die nur für eine sehr kurze Zeit gehalten werden, kann die Zeit, die für das ständige Einschlafen und Wiederaufwecken von Threads verschwendet wird, die Laufzeitleistung merklich verringern. Wenn stattdessen Spinlocks verwendet werden, haben Threads die Möglichkeit, ihr volles Laufzeitquantum auszunutzen (sie blockieren immer nur für eine sehr kurze Zeitspanne, setzen dann aber sofort ihre Arbeit fort), was zu einem viel höheren Verarbeitungsdurchsatz führt.
Die Praxis
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 nicht bekannt ist), noch können Betriebssysteme wissen, ob ein bestimmtes Stück Code für Single-Core- oder Multi-Core-Umgebungen optimiert wurde, unterscheiden die meisten Systeme nicht streng zwischen Mutexen und Spinlocks. Tatsächlich verfügen die meisten modernen Betriebssysteme über hybride Mutexe und hybride Spinlocks. Was bedeutet das eigentlich?
Ein hybrider Mutex verhält sich auf einem Multi-Core-System 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 schon bald wieder entsperrt wird. Erst 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 läuft, wird der Mutex nicht spinlock, obwohl das, wie oben erwähnt, nicht von Vorteil wäre.
Ein hybrider Spinlock verhält sich zunächst wie ein normaler Spinlock, aber um nicht zu viel CPU-Zeit zu verschwenden, kann er eine Backoff-Strategie haben. Normalerweise wird der Thread nicht schlafen gelegt (da man das bei der Verwendung eines Spinlocks nicht möchte), aber er kann entscheiden, den Thread zu stoppen (entweder sofort oder nach einer bestimmten Zeit; dies wird "Yielding" genannt) und einen anderen Thread laufen zu lassen, was die Chancen erhöht, dass der Spinlock entsperrt wird (man hat immer noch die Kosten eines Threadwechsels, aber nicht die Kosten, einen Thread schlafen zu legen und wieder aufzuwecken).
Résumé
Im Zweifelsfall sollten Sie Mutexe verwenden. Sie sind in der Regel die bessere Wahl und die meisten modernen Systeme erlauben es ihnen, für eine sehr kurze Zeit Spinlocks zu verwenden, 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 Erwägung ziehen, ein eigenes "Lock-Objekt" zu verwenden, das intern entweder einen Spinlock oder einen Mutex verwenden kann (dieses Verhalten könnte z.B. bei der Erstellung eines solchen Objekts konfiguriert werden), anfangs überall Mutexe zu verwenden und wenn Sie glauben, dass die Verwendung eines Spinlocks irgendwo wirklich helfen könnte, probieren Sie es aus und vergleichen Sie die Ergebnisse (z.B. mit einem Profiler), aber stellen Sie sicher, dass Sie beide Fälle testen, ein Single-Core- und ein Multi-Core-System, bevor Sie voreilige Schlüsse ziehen (und möglicherweise verschiedene Betriebssysteme, wenn Ihr Code plattformübergreifend sein wird).
Update: Eine Warnung für iOS
Eigentlich nicht iOS-spezifisch, aber iOS ist die Plattform, auf der die meisten Entwickler mit diesem Problem konfrontiert werden: Wenn Ihr System einen Thread-Scheduler hat, der nicht garantiert, dass jeder Thread, egal wie niedrig seine Priorität ist, irgendwann eine Chance bekommt, ausgeführt zu werden, dann können Spinlocks zu dauerhaften Deadlocks führen. Der iOS-Scheduler unterscheidet zwischen verschiedenen Klassen von Threads, und Threads einer niedrigeren Klasse werden nur ausgeführt, wenn kein Thread einer höheren Klasse ebenfalls ausgeführt werden will. Dafür gibt es keine Rückzugsstrategie. Wenn also ständig Threads hoher Klassen verfügbar sind, erhalten Threads niedriger Klassen nie CPU-Zeit und damit auch keine Chance, Arbeit zu verrichten.
Das Problem stellt sich wie folgt dar: Ihr Code erhält einen Spinlock in einem Thread einer niedrigen Prio-Klasse und während er sich inmitten dieses Locks befindet, ist das Zeitquantum überschritten und der Thread hält an. Der einzige Weg, wie dieser Spinlock wieder freigegeben werden kann, ist, wenn der Thread der niedrigen Prio-Klasse wieder CPU-Zeit erhält, aber das ist nicht garantiert. Es kann sein, dass Sie einige Threads der hohen Prio-Klasse haben, die ständig laufen wollen, und der Taskplaner wird diese immer priorisieren. Einer von ihnen könnte über den Spinlock laufen und versuchen, ihn zu erhalten, was natürlich nicht möglich ist, und das System wird ihn zur Aufgabe zwingen. Das Problem ist: Ein Thread, der aufgegeben hat, kann sofort wieder ausgeführt werden! Da er eine höhere Priorität hat als der Thread, der die Sperre hält, hat der Thread, der die Sperre hält, keine Chance, CPU-Laufzeit zu erhalten. Entweder bekommt ein anderer Thread die Laufzeit oder der Thread, der gerade nachgegeben hat.
Warum tritt dieses Problem bei Mutexen nicht auf? Wenn der High-Prio-Thread die Mutex nicht erhalten kann, gibt er nicht auf, sondern dreht sich vielleicht ein wenig, wird aber schließlich in den Schlaf geschickt. Ein schlafender Thread steht erst dann wieder zur Verfügung, wenn er durch ein Ereignis aufgeweckt wird, z.B. durch die Freigabe des Mutex, auf die er gewartet hat. Apple ist sich dieses Problems bewusst und hat die Funktion OSSpinLock
als Ergebnis. Die neue Sperre heißt os_unfair_lock
. Diese Sperre vermeidet die oben erwähnte Situation, da sie die verschiedenen Thread-Prioritätsklassen kennt. Wenn Sie sich sicher sind, dass die Verwendung von Spinlocks in Ihrem iOS-Projekt eine gute Idee ist, verwenden Sie diese Sperre. Halten Sie sich fern von OSSpinLock
! Und implementieren Sie unter keinen Umständen eigene 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 nicht den Low-Prio-Threads) erlaubt, CPU-Zeit "trocken" zu legen, dennoch kann die gleiche Situation dort auftreten und führt dann zu sehr schlechter Leistung, also OSSpinLock
ist auch unter macOS veraltet.
In Anlehnung an Meckis Vorschlag wird dieser Artikel pthread mutex vs. pthread spinlock im Blog von Alexander Sandler, Alex on Linux zeigt, wie die spinlock
& mutexes
kann implementiert werden, um das Verhalten mit #ifdef zu testen.
Allerdings sollten Sie die endgültige Entscheidung auf der Grundlage Ihrer Beobachtung treffen, da das angegebene Beispiel ein Einzelfall ist und Ihre Projektanforderungen und -umgebung völlig anders sein können.
Die Antwort von Mecki trifft es ziemlich gut. Auf einem einzelnen Prozessor könnte die Verwendung eines Spinlocks jedoch sinnvoll sein, wenn die Aufgabe darauf wartet, dass die Sperre von einer Interrupt Service Routine erteilt wird. Die Unterbrechung würde die Kontrolle an die ISR übertragen, die die Ressource für die Nutzung durch die wartende Aufgabe bereitstellen würde. Am Ende wird die Sperre freigegeben, bevor die Kontrolle an die unterbrochene Aufgabe zurückgegeben wird. Die spinnende Task würde den Spinlock verfügbar finden und fortfahren.
Bitte beachten Sie auch, dass Sie unter bestimmten Umgebungen und Bedingungen (z. B. unter Windows auf Dispatch-Level >= DISPATCH LEVEL) keinen Mutex, sondern nur Spinlock verwenden können. Unter Unix - das Gleiche.
Hier ist eine entsprechende Frage auf der Unix-Seite des Konkurrenten stackexchange: https://unix.stackexchange.com/questions/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-something-more
Informationen zum Dispatching auf Windows-Systemen: http://download.microsoft.com/download/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc
Spinlock- und Mutex-Synchronisationsmechanismen sind heute sehr häufig zu sehen.
Lassen Sie uns zuerst über Spinlock nachdenken.
Im Grunde handelt es sich um eine Busy Waiting Action, d.h. wir müssen warten, bis eine bestimmte Sperre aufgehoben ist, bevor wir mit der nächsten Aktion fortfahren können. Konzeptionell sehr einfach, während die Umsetzung ist nicht auf den Fall. Ein Beispiel: Wenn die Sperre nicht freigegeben wurde, wurde der Thread ausgelagert und geht in den Ruhezustand über. Wie geht man mit Synchronisationssperren um, wenn zwei Threads gleichzeitig Zugriff verlangen?
Im Allgemeinen ist die intuitivste Idee, die Synchronisation über eine Variable zu behandeln, um den kritischen Abschnitt zu schützen. Das Konzept der Mutex ist ähnlich, aber sie sind dennoch unterschiedlich. Fokus auf: CPU-Auslastung. Spinlock verbraucht CPU-Zeit, um auf die Ausführung der Aktion zu warten, und daher können wir den Unterschied zwischen den beiden zusammenfassen:
In homogenen Multi-Core-Umgebungen, wenn die Zeit, die für kritische Abschnitte aufgewendet wird, gering ist, sollte Spinlock verwendet werden, da die Zeit für Kontextwechsel reduziert werden kann. (Der Vergleich mit einem Kern ist nicht wichtig, da einige Systeme Spinlock in der Mitte der Umschaltung implementieren).
Unter Windows führt die Verwendung von Spinlock zu einem Upgrade des Threads auf DISPATCH_LEVEL, was in manchen Fällen nicht erlaubt ist, so dass wir dieses Mal einen Mutex (APC_LEVEL) verwenden mussten.
- See previous answers
- Weitere Antworten anzeigen