it-swarm-eu.dev

Gibt es ein Problem bei der Verwendung vieler Sperren in einem Prozess?

Ich versuche, eine C # -Anwendung zu erstellen, die Benachrichtigungen vom Server an seine Benutzer sendet (eine mobile App). Da jeder Benutzer seine Benachrichtigungssammlung von einem beliebigen Thread ändern lassen kann, muss ich diese Sammlungen sperren, wenn jemand versucht, eine Benachrichtigung zu lesen/hinzuzufügen/zu löschen.

Das Problem, mit dem ich möglicherweise konfrontiert bin, wenn viele Benutzer (hoffentlich Millionen;)) gleichzeitig angemeldet sind, besteht darin, dass ich für jede Sammlung eine separate Sperre beibehalten muss, ein Prozess jedoch nur eine begrenzte Anzahl von Benutzern enthalten kann Anzahl der Griffe und ich brauche mehr Schlösser als ich haben darf.

Ist das ein echtes Problem oder mache ich mir um nichts Sorgen? Gibt es dafür eine bessere Lösung?

16
Idov

Die Verwendung vieler Schlösser kann einige Probleme verursachen:

  • Die Anzahl der Sperren, die ein Prozess möglicherweise anfordert, wird häufig vom Betriebssystem begrenzt.
    Möglicherweise gehen Ihrem System tatsächlich die Sperren aus.
    Hinweis: Dies hängt von der Art der Sperre ab. Die Anzahl der Interprozess-Sperren wie Mutex und aller anderen von WaitHandle abgeleiteten Synchronisationsprimitive wird vom Betriebssystem begrenzt Die Compare-And-Swap-Cache-Zeilensperrvorgänge (in .net, die über die Klasse Interlocked bereitgestellt werden) sind CPU-Anweisungen und können ohne Einschränkungen verwendet werden. Kritische Abschnitte (bereitgestellt durch das Schlüsselwort lock in c # und die Klasse Monitor in .net) sind wahrscheinlich nur durch den verfügbaren Speicher begrenzt, ebenso wie ReaderWriterLockSlim und SemaphoreSlim (wie durch einen Kommentar von Greeble31 hinzugefügt).
  • Das Anfordern des Zugriffs auf ein Schloss kostet Zeit. Das Anfordern des Zugriffs auf eine Sperre, die von einem anderen Thread verwendet wird, bewirkt, dass das Betriebssystem den Thread blockiert und zu einem anderen wechselt, was ebenfalls Zeit kostet.
    Bei zu vielen Sperren und Threads verbringt Ihr Prozess möglicherweise den größten Teil seiner Zeit mit der Buchhaltung für das Sperren, anstatt Berechnungen durchzuführen, die Ihre Benutzer wünschen.
  • Wenn Ihr Prozess nicht diszipliniert ist, welche Sperren er zu erwerben versucht und in welcher Reihenfolge, kann dies zu Deadlocks führen.

Alternativen:

  • Verwenden Sie eine Event-Sourcing-Architektur (möglicherweise mit schreibgeschützter Datenprojektion (siehe CQRS)).
    Ein Event-Broker (entweder benutzerdefiniert oder im Regal) kann die Sperre übernehmen.
  • Verwenden Sie sperrfreie Algorithmen und Datenstrukturen in Kombination mit einfachen Anweisungen zum Sperren von Cache-Zeilen zum Vergleichen und Austauschen.
    Führen Sie den größten Teil der Arbeit außerhalb des Schlosses aus und tauschen Sie Zeiger auf neue Listenköpfe/Baumknoten aus. Siehe zum Beispiel Ctries von Aleksander Prokopec.
  • Verwenden Sie die (zeilenbasierten?) Sperrfunktionen der Datenbank, um die Benachrichtigung vor gleichzeitigen Aktualisierungen (einschließlich des Lese-/Ungelesen-Status der Benachrichtigung) zu schützen (wie durch einen Kommentar von Bart van Ingen Schenau hinzugefügt).
    Jede Benachrichtigung kann eine Zeile in einer Datenbanktabelle sein.
35

Wie in den Kommentaren erwähnt, treten beim Skalieren Probleme beim Versuch auf, den Status im Speicher zu halten. Was passiert, wenn Ihr Server abstürzt? Verlieren alle Ihre Benutzer ihre Benachrichtigungen? Beim Skalieren führen Sie normalerweise eine Datenbank ein, die Ihre Benachrichtigungen beibehält. Die Datenbank behandelt das Sperren in dem von Ihnen beschriebenen einfachen Fall, sodass Sie sich keine Sorgen machen müssen, bis Sie anfangen, Ihre Datenbank zu erweitern.

In Bezug auf die Parallelität während des Prozesses gibt es einige Alternativen zum Sperren:

Gleichzeitige Datenstrukturen

Gleichzeitige Datenstrukturen helfen bei der Implementierung der Sperrung für das von Ihnen beschriebene Problem. Sie können Ihre Benachrichtigungen in einer gleichzeitigen Sammlung speichern und von verschiedenen Threads aus darauf zugreifen, ohne sich selbst zu blockieren. Es wird bevorzugt, diese nach Möglichkeit zu verwenden, anstatt die Sperre selbst zu implementieren, da Sie sie mit größerer Wahrscheinlichkeit falsch oder nicht optimal implementieren.

Schauspielermodell

Ihr System ist als eine Gruppe von Akteuren modelliert, die über Nachrichten kommunizieren. Es gibt Garantien für die Nachrichtenreihenfolge und die Nachrichtenverarbeitung, die die Angemessenheit eines gleichzeitigen Systems gegenüber dem Sperren erheblich verbessern. Nachrichten werden nacheinander verarbeitet, sodass sie möglicherweise einen schlechteren Durchsatz bieten. Bei ordnungsgemäßer Ausführung kommt es jedoch nicht zu einem Deadlock, und es ist weniger wahrscheinlich, dass Sie unsicher auf Ihren Status zugreifen (da der Status für den Akteur privat ist).

Optimistische Parallelität

Bei optimistischer Parallelität halten Sie über einen längeren Zeitraum keine Sperre. Stattdessen verfolgt Ihr Client die Version des Status vom letzten Lesen des Status an. Wenn Sie dann versuchen, den Status festzulegen, überprüfen Sie, ob die Versionen noch übereinstimmen. Wenn die Versionen übereinstimmen, legen Sie den Status fest. Wenn eine Versionsinkongruenz vorliegt, bedeutet dies, dass ein anderer Thread den Status bereits geändert hat. Sie können dann eine Wiederholungsrichtlinie anwenden, um die Änderung erneut zu versuchen, indem Sie den neuesten Status abrufen, die Mutation anwenden und versuchen, den Status festzulegen. Dies ist ein guter Ansatz, wenn Sie viel weniger Schreibvorgänge als Lesevorgänge haben. Dies hat einen besseren Durchsatz als das Sperren und das Akteurmodell, wenn die meisten Ihrer Anforderungen gelesen werden.

10
Samuel