Wie synchronisiert man den Zugriff auf eine globale Variable mit sehr häufigen Lesevorgängen / sehr seltenen Schreibvorgängen?

9

Ich arbeite an der Debug-Protokollierungsinfrastruktur für eine Serveranwendung. Jeder Protokollierungspunkt im Quellcode gibt unter anderen Parametern seine Ebene (KRITISCH, FEHLER usw.) an. Also im Quellcode-Protokollierungspunkt sieht wie folgt aus:

%Vor%

ist ein Makro, das zu

erweitert wird %Vor%

Dabei ist DEBUG_LOG_LEVEL_HIGH eine vordefinierte Konstante (sagen wir 2) und CURRENT_DEBUG_LOG_LEVEL ist ein Ausdruck, der die aktuelle Debug-Protokollierungsebene des Benutzers auswertet. Der einfachste Ansatz wäre, CURRENT_DEBUG_LOG_LEVEL als:

zu definieren %Vor%

Ich möchte es dem Benutzer erlauben, die aktuelle Debug-Protokollierungsstufe während der Anwendungsausführung zu ändern, und es ist in Ordnung, dass die Änderung einige Sekunden dauert, bis sie wirksam wird. Die Anwendung ist multi-threaded und Änderungen in g_current_debug_log_level können einfach serialisiert werden (zum Beispiel von CRITICAL_SECTION ), aber um die Performance nicht zu beeinträchtigen, sollte ( CURRENT_DEBUG_LOG_LEVEL >= DEBUG_LOG_LEVEL_HIGH ) so schnell wie möglich ausgeführt werden, sodass ich keinen Thread verwenden möchte Synchronisationsmechanismus dort.

Meine Fragen sind also:

  1. Kann das Fehlen der Synchronisation in g_current_debug_log_level Lesevorgänge dazu führen, dass ein falscher Wert gelesen wird? Es sollte zwar die Anwendungsgenauigkeit nicht beeinträchtigen, da der Benutzer die aktuelle Debug-Protokollierungsstufe auf den falschen Wert gesetzt haben könnte, dies kann jedoch die Anwendungsleistung beeinträchtigen, da sie möglicherweise ein sehr hohes Debug-Protokoll für unkontrollierbare Zeit ausgeben kann.

    >
  2. Gewährleistet meine Lösung, dass die Änderung der aktuellen Debug-Protokollierungsstufe alle Threads nach der akzeptablen Zeitspanne erreicht (sagen wir ein paar Sekunden)? Idealerweise möchte ich, dass der Pegelwechselvorgang synchron ist, so dass, wenn der Benutzer eine Bestätigung bei der Pegeländerungsoperation erhält, er darauf zählen kann, dass ein nachfolgendes Protokoll entsprechend der neuen Ebene ausgegeben wird.

Ich würde auch alle Vorschläge für alternative Implementierungen, die die oben genannten Anforderungen erfüllen, sehr schätzen (minimale Auswirkung auf die Leistung für den Ebenenvergleich und die synchrone Pegeländerung mit einer Latenzzeit von nur wenigen Sekunden).

    
Sergey Kleyman 30.09.2011, 18:50
quelle

6 Antworten

4

Es gibt nichts, was erfordert, dass ein auf einem Thread in einem Kern vorgenommener Schreibvorgang jemals für einen anderen Thread sichtbar wird, der auf einem anderen Kern liest, ohne eine Art Zaun bereitzustellen, um eine "Vor-bevor" -Ecke zwischen dem Schreiben und dem Lesen zu erzeugen .

Um streng korrekt zu sein, müssten Sie nach dem Schreiben in die Log-Ebene und vor jedem Lesevorgang die entsprechenden Speicher-Fence / Barriere-Anweisungen einfügen. Fence-Operationen sind nicht billig, aber sie sind billiger als ein ausgewachsener Mutex.

In der Praxis ist es jedoch bei einer gleichzeitigen Anwendung, die Sperren an anderer Stelle verwendet, und der Tatsache, dass Ihr Programm weiterhin mehr oder weniger korrekt arbeitet, wenn der Schreibvorgang nicht sichtbar wird, wahrscheinlich, dass der Schreibvorgang zufällig sichtbar wird durch andere Umzäunungen innerhalb kurzer Zeit und erfüllen Ihre Anforderungen. So können Sie wahrscheinlich damit davonkommen, es einfach zu schreiben und die Zäune zu überspringen.

Aber das richtige Fechten zu verwenden, um die Vorkommnisse vor der Kante zu erzwingen, ist wirklich die richtige Antwort. FWIW, C ++ 11 stellt ein explizites Speichermodell bereit, das die Semantik definiert und diese Art von Fencing-Operationen auf der Sprachebene verfügbar macht. Aber soweit ich weiß, implementiert noch kein Compiler das neue Speichermodell. Also für C / C ++ müssen Sie Sperren aus einer Bibliothek oder expliziten Fencing verwenden.

    
acm 30.09.2011, 19:08
quelle
1

Angenommen, Sie arbeiten unter Windows und Windows läuft nur auf x86 (was vorerst wahr ist, sich aber ändern kann ...) und wenn Sie davon ausgehen, dass nur ein Thread in die Variable schreibt, können Sie ohne eine Synchronisierung davonkommen wie auch immer.

Um "korrekt" zu sein, sollten Sie eine Lese- / Schreibsperre in irgendeiner Form verwenden.

    
R.. 30.09.2011 19:02
quelle
0

Angesichts Ihrer derzeitigen Implementierung schlage ich vor, dass Sie sich atomare Operationen ansehen. Wenn dies nur für Windows vorgesehen ist, sehen Sie sich Interlocked Variable Access an

    
NuSkooler 30.09.2011 19:10
quelle
0

Sehen Sie sich die neuen Slim Reader / Writer-Schlösser an, die unter Vista und 7 verfügbar sind. Sie sollten das tun, was Sie wollen, mit so wenig Overhead wie möglich:

Ссылка

    
Mahmoud Al-Qudsi 30.09.2011 20:34
quelle
0

Auf x86 und x64 volatile werden nur sehr wenige direkte Kosten entstehen. Es kann einige indirekte Kosten im Zusammenhang mit dem Erzwingen des erneuten Abrufs von nicht verwandten Variablen geben (Zugriffe auf flüchtige Variablen werden als Compiler-Level-Speicherzäune für alle anderen 'address take' Variablen behandelt). Stellen Sie sich eine flüchtige Variable wie einen Funktionsaufruf vor, da der Compiler Informationen über den Zustand des Speichers während des Aufrufs verliert.

Auf Itanium hat Volatile einige Kosten, aber es ist nicht so schlimm. Auf ARM stellt der MSVC-Compiler standardmäßig keine Barrieren (und keine Sortierung) für flüchtige Elemente bereit.

Eine wichtige Sache ist, dass es mindestens einen Zugriff auf diese Log-Level-Variable in Ihrem Programm geben sollte, andernfalls könnte es in eine Konstante umgewandelt und optimiert werden. Dies könnte ein Problem sein, wenn Sie beabsichtigen, die Variable durch keinen anderen Mechanismus als den Debugger zu setzen.

    
Neeraj Singh 10.05.2012 07:56
quelle
0

Definieren Sie die im Scope sichtbare Ordinalvariable und aktualisieren Sie sie entsprechend (wenn sich der Log-Level ändert) Wenn die Daten korrekt ausgerichtet sind (d. H. Die Standardeinstellung), brauchen Sie nichts Spezielles, außer dass Sie Ihre aktuelle Protokollvariable als "flüchtig" deklarieren. Dies würde für LANGE Größe (32-Bit-Ordinalzahl) funktionieren. Ihre Lösung wäre also:

%Vor%

Keine externe Synchronisation erforderlich (d. h. RWlock / CriticalSection / Spin usw.)

    
Valeri Atamaniouk 19.05.2012 21:44
quelle