45 Stimmen

Spinlocks, wie nützlich sind sie?

Wie oft verwenden Sie tatsächlich Spinlocks in Ihrem Code? Wie häufig kommt es vor, dass die Verwendung einer Busy-Loop die Verwendung von Sperren übertrifft?
Wenn ich persönlich einen Code schreibe, der Threadsicherheit erfordert, neige ich dazu, ihn mit verschiedenen Synchronisationsprimitiven zu vergleichen, und soweit es geht, scheint die Verwendung von Sperren eine bessere Leistung als die Verwendung von Spinlocks zu bringen. Unabhängig davon, wie lange ich die Sperre tatsächlich halte, ist die Menge an Konflikten, die ich bei der Verwendung von Spinlocks erhalte, weitaus größer als die Menge, die ich bei der Verwendung von Sperren erhalte (natürlich führe ich meine Tests auf einer Multiprozessormaschine durch).

Mir ist klar, dass es wahrscheinlicher ist, in "Low-Level"-Code auf ein Spinlock zu stoßen, aber mich interessiert, ob Sie es auch in einer High-Level-Programmierung nützlich finden?

5 Stimmen

Im Großen und Ganzen können Spinlocks "die Arbeit erledigen" (Synchronisation). Ich beobachte dies, um eine Antwort auf "Gibt es irgendwelche Situationen in C#, wo ein Spinlock eine geeignete Synchronisationsmethode ist?" zu sehen.

45voto

jalf Punkte 235501

Das hängt davon ab, was Sie tun. Bei allgemeinem Anwendungscode sollten Sie Spinlocks vermeiden.

Bei Low-Level-Sachen, bei denen man die Sperre nur für ein paar Instruktionen hält und die Latenzzeit wichtig ist, kann ein Spinlock eine bessere Lösung sein als eine Sperre. Aber diese Fälle sind selten, vor allem in der Art von Anwendungen, wo C# typischerweise verwendet wird.

33 Stimmen

Insbesondere, wenn Sie mehrere Kerne haben, kann es viel, viel schneller sein, einen Kern laufen zu lassen, während Sie auf einen anderen Kern warten, als einen neuen Zeitplan zu erstellen. Kontextwechsel können sich summieren, wenn es zu vielen Konflikten kommt. Bei einem einzelnen Kern sind Spinlocks weniger überzeugend, da es darauf hinausläuft, den Rest der Zeitscheibe aufzugeben, ohne den nächsten Thread hereinzulassen, bis die Zeitscheibe abläuft, so dass es auf einen wirklich langsamen Ertrag hinausläuft...

27voto

Reed Copsey Punkte 536986

In C# sind "Spin Locks" meiner Erfahrung nach fast immer schlechter als eine Sperre - es ist ein seltener Fall, in dem Spin Locks besser sind als eine Sperre.

Das ist jedoch nicht immer der Fall. .NET 4 fügt eine System.Threading.SpinLock Struktur. Dies bietet Vorteile in Situationen, in denen eine Sperre für eine sehr kurze Zeit gehalten wird und wiederholt gegriffen wird. Aus den MSDN-Dokumenten zu Datenstrukturen für die parallele Programmierung :

In Szenarien, in denen eine kurze Wartezeit für die Sperre zu erwarten ist, bietet SpinLock eine bessere Leistung als andere Formen der Sperre.

Spin Locks können andere Locking-Mechanismen in Fällen, in denen Sie so etwas wie Sperren durch einen Baum tun - wenn Sie nur mit Sperren auf jedem Knoten für eine sehr, sehr kurze Zeitspanne sind, können sie eine traditionelle Sperre auszuführen. Ich bin in einer Rendering-Engine mit einem Multithreading-Szenen-Update darauf gestoßen - Spin Locks haben sich als leistungsfähiger erwiesen als Sperren mit Monitor.Enter.

15voto

T.E.D. Punkte 42630

Bei meiner Arbeit in Echtzeit, insbesondere bei Gerätetreibern, habe ich sie oft verwendet. Es hat sich herausgestellt, dass (als ich das letzte Mal die Zeit gemessen habe) das Warten auf ein Sync-Objekt wie eine Semaphore, die an einen Hardware-Interrupt gebunden ist, mindestens 20 Mikrosekunden verschlingt, unabhängig davon, wie lange es tatsächlich dauert, bis der Interrupt auftritt. Eine einzelne Überprüfung eines speichergebundenen Hardwareregisters, gefolgt von einer Überprüfung von RDTSC (um eine Zeitüberschreitung zu ermöglichen, damit die Maschine nicht blockiert wird), liegt im hohen Nannosekundenbereich (im Grunde genommen im Rauschen). Für ein Handshaking auf Hardware-Ebene, das nicht viel Zeit in Anspruch nehmen sollte, ist ein Spinlock nur schwer zu schlagen.

0 Stimmen

13 Jahre später zur Klärung: Wenn es sich nur um eine einzige Prüfung handelt, sind 20us nicht wirklich viel. Also ja, für High-Level-Code ist es die möglichen Probleme wahrscheinlich nicht wert. In Fällen jedoch, in denen es tatsächlich mehrere Handshakes auf dieser Ebene gibt, die bei jeder E/A stattfinden müssen, können sich ein paar davon wirklich summieren.

13voto

Remus Rusanu Punkte 280155

Meine 2c: Wenn Ihre Aktualisierungen einige Zugriffskriterien erfüllen, sind sie gute Spinlock-Kandidaten:

  • schnell d.h. Sie werden Zeit haben, den Spinlock zu erwerben, die Aktualisierungen durchzuführen und den Spinlock in einem einzigen Thread-Quantum freizugeben, so dass Sie nicht vorzeitig unterbrochen werden, während Sie den Spinlock halten
  • lokalisiert alle Daten, die Sie aktualisieren, befinden sich vorzugsweise auf einer einzigen Seite, die bereits geladen ist, Sie wollen keinen TLB-Miss, während Sie den Spinlock halten, und Sie wollen definitiv keinen Page Fault Swap Read!
  • atomar Sie benötigen keine andere Sperre, um die Operation durchzuführen, d.h. warten Sie nie auf Sperren unter spinlock.

Für alles, was das Potenzial hat, sich zu lösen, sollten Sie eine benachrichtigte Sperrstruktur verwenden (Ereignisse, Mutex, Semaphoren usw.).

0 Stimmen

Kernelmodus-Sperrmechanismen sind im Vergleich zu Benutzermodus-Sperrmöglichkeiten viel langsamer, manchmal bis zu 100-mal! Daher sind Mutex usw. möglicherweise keine Option für eine wirklich gute Implementierung.

10voto

dsimcha Punkte 65784

Ein Anwendungsfall für Spin-Locks ist, wenn Sie sehr wenig Konkurrenz erwarten, aber viele davon haben werden. Wenn Sie keine Unterstützung für rekursives Sperren benötigen, kann ein Spinlock in einem einzigen Byte implementiert werden, und wenn die Konkurrenz sehr gering ist, ist die Verschwendung von CPU-Zyklen vernachlässigbar.

In der Praxis habe ich oft Arrays mit Tausenden von Elementen, bei denen Aktualisierungen an verschiedenen Elementen des Arrays sicher parallel erfolgen können. Die Wahrscheinlichkeit, dass zwei Threads versuchen, dasselbe Element zur gleichen Zeit zu aktualisieren, ist sehr gering (geringe Konkurrenz), aber ich brauche eine Sperre für jedes Element (ich werde viele davon haben). In diesen Fällen weise ich normalerweise ein Array von ubytes zu, das genauso groß ist wie das Array, das ich parallel aktualisiere, und implementiere Spinlocks inline als (in der Programmiersprache D):

while(!atomicCasUbyte(spinLocks[i], 0, 1)) {}
    myArray[i] = newVal;
atomicSetUbyte(spinLocks[i], 0);

Wenn ich hingegen reguläre Sperren verwenden müsste, müsste ich ein Array mit Zeigern auf Objekte zuweisen und dann für jedes Element dieses Arrays ein Mutex-Objekt zuweisen. In Szenarien, wie dem oben beschriebenen, ist dies einfach nur verschwenderisch.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X