Das Problem der in Listing E gezeigten Lösung besteht in der Tatsache, dass falls 100 Threads gleichzeitig zu schreiben versuchen, dies nur jeweils einem gelingt, während die anderen auf den Lock warten. Je nach Art des zugehörigen Streams kann die Ausführung von m_underlyingLog
In der Funktion internal_thread_safe_log::write_message werden die Mitteilungen an eine Warteschlange angehängt. Ein spezieller Thread liest aus dieser Warteschlange und schreibt die Mitteilungen in den zugehörigen Stream. Die Funktion internal_thread_safe_log::write_message enthält nun lediglich zusätzlich einen Pointer auf eine Warteschlange, was fast gar keine Zeit erfordert, jedoch Engpässe vermeidet. Listing G zeigt die verbesserte Lösung.
Hier der genaue Unterschied zu der vorherigen Version:
Es ist Aufgabe des Thread-Managers, plattformabhängige Threading-Vorgänge abzukoppeln (beispielsweise das Erstellen eines neuen Threads). Auf den Thread-Manager wird später noch detaillierter eingegangen, für den Moment ist der aktuelle Thread-Manager jedenfalls win32_thread_manager. Die endgültige Lösung wird auch andere Thread-Manager zulassen.
Lösung 3
Je nach der Anzahl der Protokolle und/oder Threads, die die Anwendung verwenden soll, könnte die Lösung 2 nur wenig geeignet sein. Mehrere Protokolle könnten einen einzelnen Thread gemeinsam nutzen, der alle Protokolle schreibt. Lösung 3 bietet diese Funktionalität dank der folgenden Komponenten:
Als Beispiel hierzu drei Protokolle: Protokoll 1 hat die Priorität 6, Protokoll 2 hat die Priorität 3 und Protokoll 3 hat die Priorität 1. (Die Prioritäten addieren sich also auf 10 = 6 + 3 + 1.) Im Writer-Thread erfolgen von 10 Schreibvorgängen sechs in Protokoll 1, drei in Protokoll 2 und einer in Protokoll 3. Wenn ein Schreibvorgang in ein Protokoll versucht wird, in dessen Warteschlange sich keine Mitteilung befindet, wird der Schreibvorgang ignoriert.
Der Test wurde wie in Listing H abgeändert und enthält nun die folgenden Funktionen:
Beim Ausführen von Listing H ist zu beachten, dass die Protokolle mit höheren Zahlen aufgrund ihrer Priorität ein wenig schneller als die anderen gefüllt werden.
Lösung 4
Lösung 4 ermöglicht die Auswahl zwischen Lösung 2 und Lösung 3, je nach den jeweiligen Anwendungsanforderungen. Zum Wechsel von einer Lösung zur anderen müssen nur ein oder zwei Codezeilen in den get_log()-Funktionen geändert werden. Für eine solche Multiformat-Lösung sind die folgenden Komponenten erforderlich:
Listing I zeigt, wie einfach der Wechsel von internal_thread_safe_log_ownthread zu internal_thread_safe_log_sharethread und umgekehrt ist. Listing J stellt die gesamte Implementierung von Lösung 4 dar.
Listing J enthält die folgenden zusätzlichen Funktionen:
Das Auslassen des Leerens führt sehr wahrscheinlich zu Fehlern, wie Listing L zeigt. Daher wird der Leerungsvorgang vor dem Löschen jeder temporären Variablen zu einer Bedingung gemacht, wie in Listing M dargestellt. Die Implementierung dieser Bedingung ist ganz einfach: siehe thread_safe_log::on_last_message.
Im Destruktor thread_safe_log ist die Handhabung ungültiger Referenzen ausgeschlossen. Die Verwendung von temporären Variablen bringt jedoch ein spezielles Problem mit sich: die Nutzung dieser temporären Variablen nach deren Löschen ([temp-destructed]).
Der Test hat sich ebenfalls ein wenig verändert:
Zuletzt sollen noch die Thread-Manager näher betrachtet werden. Ein Thread-Manager ist eine Klasse, die vorgibt, wie bestimmte Threading-Vorgänge zu handhaben sind. Er muss die folgenden Komponenten liefern:
Es werden zwei Thread-Manager bereitgestellt:
Im Code kann über #define festgelegt werden, dass USE_WIN32_THREAD_MANAGER standardmäßig den ersteren Thread-Manager verwendet und USE_BOOST_THREAD_MANAGER den letzteren Thread-Manager. Man kann auch einen eigenen Thread-Manager bereitstellen und über #define DEFAULT_THREAD_MANAGER als your_threading_manager_class festlegen.
Nun ist Listing J auszuführen (wohlgemerkt, dazu ist der letzte message_handler_log.h erforderlich – Listing K). Dabei fällt auf, dass Listing J eine ganze Menge Klassen enthält. Aus diesem Grund wurden sie auf mehrere Dateien verteilt.
Komplex, aber machbar
Durch Nutzung der Klassen thread_safe_log und internal_thread_safe_log_* kann man die Protokollierung in gewohnter Weise durchführen und die Vorteile der Thread-sicherheit nutzen, ohne wesentliche Abstriche in puncto Effizienz machen zu müssen. Vorhandene Codes sowie Utility-Funktionen und -Klassen mit Nutzung von STL-Streams können so auf thread-sichere Weise eingesetzt werden. Auch das Refactoring bestehender Anwendungen ist problemlos möglich. Thread-sicherheit ist ein komplexes Thema, das sich jedoch mit ein wenig Aufwand meistern lässt und zahlreiche Vorteile bietet.
Vernetzte Produkte müssen laut Cyber Resilience Act über Möglichkeiten zur Datenverschlüsselung und Zugangsverwaltung verfügen.
Das jüngste Update für Windows, macOS und Linux stopft drei Löcher. Eine Anfälligkeit setzt Nutzer…
Zwei von Google-Mitarbeitern entdeckte Schwachstellen werden bereits aktiv gegen Mac-Systeme mit Intel-Prozessoren eingesetzt. Sie erlauben…
Die Hintermänner haben es unter anderem auf Daten von Facebook-Geschäftskonten abgesehen. Opfer werden über angebliche…
Bis 2027 werden 90 Prozent der Unternehmen eine Hybrid-Cloud-Strategie umsetzen.
Apple belegt in der Statistik von Counterpoint die ersten drei Plätze. Samsungs Galaxy S24 schafft…