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?
Die Verwendung vieler Schlösser kann einige Probleme verursachen:
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).Alternativen:
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 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.
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).
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.