boost::shared_mutex
lub std::shared_mutex
(C++ 17) może być używana dla pojedynczego programu piszącego, dostęp do wielu czytników. Jako ćwiczenie edukacyjne, stworzyłem prostą implementację, która wykorzystuje spinlocking i ma inne ograniczenia (np. Polityka uczciwości), ale oczywiście nie jest przeznaczona do użycia w rzeczywistych aplikacjach.Implementacja C++ shared_mutex
Pomysł polega na tym, że muteks zachowuje liczbę referencyjną równą zero, jeśli żaden wątek nie trzyma blokady. Jeśli> 0, wartość reprezentuje liczbę czytników, które mają dostęp. Jeśli -1, pojedynczy program piszący ma dostęp.
Czy jest to poprawna implementacja (w szczególności przy użyciu minimalnej ilości pamięci), która nie zawiera wyścigów danych?
#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);
}
};
Nie byłem pewien co do kolejności pamięci w unlock_shared, ale moje rozumowanie polegało na tym, że tak naprawdę nie "wypuszcza" niczego, ponieważ ma dostęp tylko do odczytu i nie może zmienić danych, które chroni. – LWimsey
@ LWimsey: tak, to jest trudniejsze myśleć o ładowaniu zamówień niż zamawianiu sklepu, ale to naprawdę. Obciążenie staje się globalnie widoczne w chwili odczytu danych z pamięci podręcznej L1. (Ponieważ wtedy robi się kopię ze spójnej globalnie pamięci podręcznej na rdzeń nieużywanego pojedynczego procesora.) –