2017-02-06 6 views
0

I umgesetzt einen Spin-Lock mit C++ 11 Atom-Bibliothek:Warum funktioniert Spinlock mit std :: memory_order_relaxed korrekt?

class SpinLock { 
    atomic_bool latch_; 

    public: 
    SpinLock() :latch_(false){ 
    } 
    void lock() { 
    while(tryLock() == false); 
    } 
    bool tryLock() { 
    bool b = false; 
    return latch_.compare_exchange_weak(b,true,std::memory_order_relaxed); 
    } 
    void unlock() { 
    latch_.store(false,std::memory_order_relaxed); 
    } 
}; 

I durch Laichen mehr Threads, die Richtigkeit wie folgt getestet:

static int z = 0; 
static SpinLock spinLock; 
static void safeIncrement(int run) { 
    while(--run >= 0) { 
    std::lock_guard<SpinLock> guard(spinLock); 
    ++z; 
    } 
} 

static void test(int nThreads =2) { 
    std::vector<std::thread*> workers(nThreads); 
    z = 0; 
    for(auto& ptr : workers) ptr = new std::thread(safeIncrement,1<<20); 
    for(auto ptr : workers) ptr->join(); 
    cout<<"after increment: " <<z << " out of " << (1<<20) * nThreads<<endl; 
    for(auto ptr : workers) delete ptr; 
} 
int main() { 

    test(4); 

    return 0; 

} 

Ich bin die insgesamt am Ende überrascht fügt als richtiger Wert mit entspannter Reihenfolge. Durch diesen Artikel: http://en.cppreference.com/w/cpp/atomic/memory_order bedeutet lockere Reihenfolge "es gibt keine Synchronisation oder Reihenfolge Einschränkungen", so dass die Änderung von einem Thread nicht von anderen sichtbar sein soll, oder? Warum ist es immer noch richtig?
(Der Test läuft auf Intel (R) Core (TM) i5-3337U CPU @ 1.80GHz)

EDIT: (dank Maxim Kommentar) Aktualisiert das Code: Initialisierung Datenelement in dem SpinLock, und Aktualisieren des Testcodes.

+0

Stellen Sie den vollständigen Testquellcode bereit. –

+0

Es gibt keine Initialisierung von 'SpinLock :: latch_', der Anfangswert ist unbestimmt. –

Antwort

1

Der C++ 11-Standard spezifiziert die schwächsten Garantien für die atomaren Operationen. Nicht jede Hardware kann jede schwächste Garantie genau abbilden, und so müssen der Compiler und der Bibliotheksschreiber manchmal zu stärkeren Operationen "aufrunden". Zum Beispiel haben alle atomaren Read-Modify-Write-Operationen auf x86 implizit memory_order_acq_rel.

Darüber hinaus können bestimmte Implementierungen einer Hardware-Architektur stärkere Garantien haben, als die Hardware-Handbücher sagen. Zum Beispiel haben die frühen Itanium-Versionen die Semantik memory_order_acq_rel sogar für einige Hardware-Anweisungen implementiert, die nur memory_order_release versprochen haben.

Theoretisch ist es möglich, dass Ihr Code auf x86 fehlschlägt, da die Speicherordnung von atomaren Operationen sowohl der Hardware und Compiler zu beachten ist. Ein aggressiver Compiler könnte die Last von 'z' (und möglicherweise auch den Speicher zu!) Über die tryLock Operation, die nur memory_order_release Befehle verwendet, legal bewegen.

+0

Ein guter Punkt, den ich ausgelassen habe, ist, dass "release" die Synchronisation nur durchführt, wenn (dynamisch) gepaart mit einem "acquire" am * selben * Ort. Da Sie 'spinLock' als statisch deklariert haben und alle Operationen für den Compiler sichtbar sind, könnte der Compiler beobachten, dass es niemals" acquire "-Operationen für" spinLock "gibt und alle Ihre Operationen auf der Variablen' spinLock' auf '' setzen memory_order_relaxed'. –

1

Ich sehe, dass mindestens GCC 6.3 bei x86-64 den gleichen Code für entspannt und für Freigabe/erwerben generiert. Daher ist es nicht verwunderlich, dass die Ergebnisse gleich sind. Also, um den Unterschied zu sehen, möchten Sie vielleicht mehr Speicherarchitektur als TSO, dass x86-64 bietet. Wahrscheinlich könnte es ARM sein.

+0

Es ist möglich, dass 'z' einen erwarteten Wert hat, weil es als' std :: atomic 'deklariert wird, oder' run' nicht positiv ist oder weil es nur einen Thread gibt. –

0

Die Verwendung einer lockeren Sortierbedingung für eine Mutex-Implementierung ist ein Rezept für eine Katastrophe.
Mutexes sollen per Definition Daten zwischen Threads synchronisieren. Die Begriffe erwerben und Freigabe sind stark mit einem Mutex verbunden; Sie erwerben ein Mutex, ändern Sie die Daten durch sie geschützt und Release der Mutex, so dass die Daten für einen anderen Thread sichtbar wird, sobald es den gleichen Mutex erwirbt.

Der Artikel, den Sie auf Staaten beziehen, die für entspannte Operationen „es keine Synchronisation oder Ordnungszwänge sind“ ... Es gilt für Operationen Speicher den Mutex umgibt, nicht den Mutex selbst. Bei einer entspannten Anordnung können Daten, die durch den Mutex geschützt werden sollen, tatsächlich gleichzeitig durch mehrere Threads modifiziert werden (wodurch ein Datenrennen eingeführt wird).

Auf einer stärker geordneten Architektur wie X86, die implizite Erwerb/Release-Semantik hat, kommen Sie mit dieser Implementierung durch (daher sind Ihre Tests erfolgreich). Führen Sie es jedoch auf einer Architektur aus, die eine schwächere Speicherreihenfolge wie Power oder ARMv7 verwendet, und Sie sind in Schwierigkeiten.

Die Bestellungen, die Sie in den Kommentaren vorschlagen, sind korrekt.

+0

"Speicheroperationen, die den Mutex umgeben" könnten die Operationen auf dem Mutex selbst beinhalten, richtig? –

+0

@LeoLai Operationen am Mutex selbst sind atomar, da sie nur den internen 'atomic_bool' modifizieren - das bekommst du auch bei entspannter Ordnung. "Speicheroperationen, die den Mutex umgeben" sind jedoch Operationen, die vor oder nach dem Mutex (in Programmreihenfolge) kommen. Da ein Mutex wird verwendet, es zwischen Threads zu synchronisieren, muss zwischen Speicheroperationen vor dem ‚unlock/Release‘ zur Durchsetzung der Einrichtung, und nach dem ‚Verriegelungs/acquire‘ – LWimsey

+0

..... ..... An ‚unlock/Release 'Operation auf einem Mutex _must_ Speicheroperationen zu verhindern, dass es (in der Reihenfolge des Programms) voraus bewegt über den Mutex nach unten, während eines ‚lock/acquire Betriebes‘ _must_ Speicheroperationen zu verhindern, dass es folgt Bewegung über den Mutex auf. Diese Reihenfolge wird garantiert, indem die Acquire/Release-Barrieren für die atomic_bool-Operationen in der Mutex-Implementierung gesetzt werden. – LWimsey

Verwandte Themen