2016-12-16 6 views
7

boost::shared_mutex oder std::shared_mutex (C++ 17) kann für einzelne Writer, mehrere Leser Zugriff verwendet werden. Als Bildungsübung habe ich eine einfache Implementierung zusammengestellt, die Spinlocking verwendet und andere Einschränkungen (z. B. Fairness-Richtlinie) hat, aber offensichtlich nicht für die Verwendung in realen Anwendungen gedacht ist.C++ shared_mutex Implementierung

Die Idee ist, dass der Mutex eine Referenzzählung behält, die Null ist, wenn kein Thread die Sperre hält. Wenn> 0, repräsentiert der Wert die Anzahl der Leser, die Zugriff haben. Wenn -1, hat ein einzelner Schreiber Zugriff.

Ist dies eine korrekte Implementierung (insbesondere bei den verwendeten, minimalen, Speicherbestellungen), die frei von Datenrennen ist?

#include <atomic> 

class my_shared_mutex { 
    std::atomic<int> refcount{0}; 
public: 

    void lock() // write lock 
    { 
     int val; 
     do { 
      val = 0; // Can only take a write lock when refcount == 0 

     } while (!refcount.compare_exchange_weak(val, -1, std::memory_order_acquire)); 
     // can memory_order_relaxed be used if only a single thread takes write locks ? 
    } 

    void unlock() // write unlock 
    { 
     refcount.store(0, std::memory_order_release); 
    } 

    void lock_shared() // read lock 
    { 
     int val; 
     do { 
      do { 
       val = refcount.load(std::memory_order_relaxed); 

      } while (val == -1); // spinning until the write lock is released 

     } while (!refcount.compare_exchange_weak(val, val+1, std::memory_order_acquire)); 
    } 

    void unlock_shared() // read unlock 
    { 
     refcount.fetch_sub(1, std::memory_order_relaxed); 
    } 
}; 

Antwort

3

(Ich verwende cmpxchg als Kürzel für die C++ compare_exchange_weak Funktion, nicht die cmpxchg instruction).

lock_shared sieht auf jeden Fall gut aus: Drehen auf einen Lesevorgang und nur einen cmpxchg versuchen, wenn der Wert für die Leistung viel besser ist als beim Drehen auf cmpxchg. Obwohl ich denke, dass Sie für die Korrektheit dazu gezwungen wurden, vermeiden Sie es, -1 zu 0 zu ändern und eine Schreibsperre zu entsperren.

Ich denke, unlock_shared sollte mo_release verwenden, nicht mo_relaxed, da sie die Lasten aus der gemeinsam genutzten Datenstruktur bestellen muss sicherstellen, dass ein Schriftsteller nicht schreibt nicht beginnen, bevor die Lasten aus dem Leser kritischen Abschnitt passieren. (LoadStore reordering ist eine Sache auf schwach geordneten Architekturen, obwohl x86 nur StoreLoad neu anordnet.) A Release operation will order preceding loads and keep them inside the critical section.


(in Schreib lock): // kann memory_order_relaxed wenn nur dauert ein einzelner Thread Schreibsperren verwendet werden?

Nein, müssen Sie noch die Schreibvorgänge in dem kritischen Abschnitt zu halten, so wie vor den cmpxchg Mit synchronisieren muss (in C++ Terminologie) Release-Shops von unlock_shared.

+0

Ich war mir nicht sicher über die Speicherreihenfolge in unlock_shared, aber meine Argumentation war, dass es wirklich nichts "freigibt", da es nur Lesezugriff hat und die Daten nicht ändern kann. – LWimsey

+0

@LWimsey: Ja, es ist schwieriger über die Bestellung von Ladungen nachzudenken, als die Bestellung von Geschäften, aber es ist eine echte Sache. Eine Ladung wird global sichtbar, wenn Daten aus dem L1-Cache gelesen werden. (Da wird dann eine Kopie vom global-kohärenten Cache in den Out-of-Order-Core einer einzelnen CPU kopiert.) –