Nehmen wir an, ich habe ein Multithread-C ++ - Programm, das Anfragen in Form eines Funktionsaufrufs an handleRequest(string key)
bearbeitet. Jeder Aufruf von handleRequest
erfolgt in einem separaten Thread und es gibt eine beliebig große Anzahl möglicher Werte für key
.
Ich möchte folgendes Verhalten:
handleRequest(key)
werden serialisiert, wenn sie für key
den gleichen Wert haben. Der Körper von handleRequest
könnte so aussehen:
Frage: Wie würde ich KeyLock
implementieren, um das erforderliche Verhalten zu erhalten?
Eine naive Implementierung könnte so beginnen:
%Vor% ... aber das erfordert eine globale Sperre am Anfang und Ende jeder Anfrage und die Erstellung eines separaten Lock
-Objekts für jede Anfrage. Wenn der Konflikt zwischen den Aufrufen von handleRequest
hoch ist, ist das möglicherweise kein Problem, aber es könnte viel Overhead verursachen, wenn die Konkurrenz gering ist.
Sie könnten etwas ähnliches wie in Ihrer Frage tun, aber statt einer einzigen global_key_map haben Sie mehrere (wahrscheinlich in einem Array oder Vektor) - welche verwendet wird, wird durch eine einfache Hash-Funktion in der Zeichenfolge bestimmt.
Auf diese Weise verteilen Sie das anstelle einer einzelnen globalen Sperre auf mehrere unabhängige.
Dies ist ein Muster, das oft in Speicherzuordnern verwendet wird (ich weiß nicht, ob das Muster einen Namen hat - es sollte). Wenn eine Anfrage eingeht, bestimmt etwas, aus welchem Pool die Zuweisung stammt (normalerweise die Größe der Anfrage, aber auch andere Parameter können berücksichtigt werden), dann muss nur dieser Pool gesperrt werden. Wenn eine Zuordnungsanforderung von einem anderen Thread eingeht, der einen anderen Pool verwendet, gibt es keine Sperrkonkurrenz.
Es hängt von der Plattform ab, aber die zwei Techniken, die ich ausprobieren würde, wären:
Beide Techniken hängen von den Details Ihres Betriebssystems ab. Experimentieren Sie und sehen Sie, was funktioniert. .
Vielleicht wäre ein std::map<std::string, MutexType>
, was Sie wollen, wobei MutexType
der Typ des gewünschten Mutex ist. Wahrscheinlich müssen Sie die Zugriffe auf die Map in einem anderen Mutex umbrechen, um sicherzustellen, dass kein anderer Thread zur gleichen Zeit eingefügt wird (und denken Sie daran, die Überprüfung erneut durchzuführen, nachdem der Mutex gesperrt wurde, um sicherzustellen, dass ein anderer Thread den Thread nicht hinzugefügt hat während Sie auf den Mutex warten!).
Das gleiche Prinzip könnte für jede andere Synchronisierungsmethode gelten, beispielsweise für einen kritischen Abschnitt.
Erhöhen Sie die Granularität und sperren Sie ganze Schlüsselbereiche
Dies ist eine Variante der Antwort von Mike B, bei der Sie anstelle mehrerer flüssiger Sperrkarten eine einzige feste Reihe von Sperren haben, die für Schlüsselbereiche statt für einzelne Schlüssel gelten.
Vereinfachtes Beispiel: Erstellen Sie beim Start ein Array mit 256 Sperren, und verwenden Sie dann das erste Byte des Schlüssels, um den Index der zu erfassenden Sperre zu bestimmen (d. h. alle mit 'k' beginnenden Schlüssel werden von locks[107]
geschützt).
Um den optimalen Durchsatz aufrechtzuerhalten, sollten Sie die Verteilung der Schlüssel und die Konkurrenzrate analysieren. Die Vorteile dieses Ansatzes sind null dynamische Zuordnungen und einfache Bereinigung; Sie vermeiden auch eine zweistufige Verriegelung. Der Nachteil sind mögliche Konkurrenzspitzen, wenn die Schlüsselverteilung im Zeitverlauf verzerrt wird.
Nachdem Sie darüber nachgedacht haben, könnte ein anderer Ansatz etwa so aussehen:
handleRequest
, erstellen Sie eine Callback
, die die eigentliche Arbeit erledigt. multimap<string, Callback*> global_key_map
, geschützt durch einen Mutex. key
bereits verarbeitet wird, fügt er Callback*
zu global_key_map
hinzu und gibt Folgendes wurde implementiert:
%Vor%Das hat den Vorteil, Threads freizugeben, die sonst auf eine Tastensperre warten würden, aber abgesehen davon ist es ziemlich genau so wie die naive Lösung, die ich in der Frage gepostet habe.
Es könnte jedoch mit den Antworten von Mike B und Constantin kombiniert werden.
Tags und Links c++ multithreading concurrency locking