2017-02-02 3 views
1

Say zwei Funktionen gibt es den Durchschnitt einer Eigenschaft zu aktualisieren und zurück gemessen wird:Veröffentlichung Semantics erwerben Berechne Durchschnitt

void Class::Update(int delta) 
{ 
    m_accumulatedValue += delta; 
    ++ m_count; 
} 

double Class::GetAverage() 
{ 
    return m_accumulatedValue/(double)m_count; 
} 

Nun nehmen wir sie in einer Multithread-Umgebung mit einem Gewinde geändert werden müssen laufen Pool, in dem ein Thread angefordert wird einen von ihnen auszuführen - das heißt, der Faden jedem von ihnen die Ausführung ein anderer jedes Mal sein:

std::atomic<int> m_accumulatedValue; 
std::atomic<int> m_count; 

// ... 

void Class::Update(int delta) 
{ 
    m_accumulatedValue.fetch_add(delta , std::memory_order_relaxed); 
    m_count.fetch_add(1 , std::memory_order_release); 
} 

double Class::GetAverage() 
{ 
    auto count = m_count.load(std::memory_order_acquire); 
    auto acc = m_accumulatedValue.load(std::memory_order_relaxed); 

    return acc/(double)count; 
} 

ich versuche, die acquire zu verstehen und Speicher freigeben Bestellungen.

Angenommen, es gibt keine gleichzeitigen Aufrufe desselben Objekts für Update(), aber möglicherweise gleichzeitige Aufrufe desselben Objekts für Update() und GetAverage().

Für das, was ich gelesen habe, verbietet die acquire Last von m_count in GetAverage() die Umordnung der Last von m_accumulatedValue, bevor er und zugleich garantiert, dass jede Änderung m_accumulatedValue ausgeführt durch Update() durch den Thread calling sichtbar ist GetAverage() Sobald der Wechsel zu m_count ist auch zu sehen, für den Speicher durchgeführt m_cout von Update() hat eine Freigabe bestellen.

Ist das, was ich gerade gesagt habe, richtig?

Gibt GetAverage() (mit der besagten Garantie der Nicht-Gleichzeitigkeit der Anrufe zu Update()) immer die richtige Antwort zurück? Oder kann es einen Weg geben, den berechneten Durchschnitt mit einigen der Werte "aktueller" als den anderen zurück zu geben?

Muss m_accumulatedValue überhaupt atomar sein?

+1

'memory_order_relaxed' verbietet dem anderen Thread nicht, den neuen Wert zu sehen, es erfordert es einfach nicht.Es könnte also immer noch eine Wettlaufsituation geben. –

+2

Abgesehen von der Semantik der Speicherordnung ist dieser Code gebrochen. Eine mögliche Ausführungsreihenfolge besteht darin, dass ein Thread zu 'm_accumulatedValue' addiert, dann ein anderer Thread' m_accumulatedValue' ** und ** 'm_count' liest, dann aktualisiert der erste Thread' m_count'. Der vom zweiten Thread berechnete Durchschnittswert ist falsch. –

+0

Nein. Vielleicht. Warum nicht gegenseitiger Ausschluss? – jotik

Antwort

0

Ihre Beschreibung, wie die Semantik Erwerb/Freigabe funktioniert, ist korrekt; Sie werden verwendet, um einen Inter-Thread zu erstellen passiert vor Beziehung zwischen Speicheroperationen vor dem Speichern/release und nach dem Laden/Erfassen ... Dies basiert auf einer Laufzeitbeziehung und ist nur definiert, wenn Die atomare Ladung/Erwerbung sieht den Wert, der durch die Speicherung/Freigabe festgelegt wurde.

Das erste Problem mit Ihrem Code ist, dass es diese Laufzeitanforderung nicht erfüllt. Der Wert von m_count wird nicht geprüft und daher gelten die Bestellgarantien nicht; Sie könnten also auch memory_order_relaxed für alle Operationen verwendet haben.

Aber das allein löst das Problem nicht; Wenn Sie m_accumulatedValue lesen, hat sich sein Wert möglicherweise durch einen anderen Aufruf zu Update() geändert (m_accumulatedValue muss daher atomar sein). Weiterhin, wie im Abschnitt Kommentare erwähnt, da 0 Atomaritäten zwischen den atomaren Operationen keine ist, kann GetAverage() aufgerufen werden, bevor Update() beendet ist und einen falschen Wert zurückgeben.

Was Sie brauchen, ist eine strikte Bestellung zwischen Update() und GetAverage() und der beste Weg, das zu tun ist mit einem std::mutex. Die atomaren Variablen können dann reguläre Ganzzahlen sein (falls nicht anderswo verwendet).

Verwandte Themen