Following the example from C++ shared_mutex implementation, I have written my own shared_mutex implementation with compare_exchange_weak. Here are my changes:
- added
try_lock()andtry_lock_shared()methods. - using the default memory model for all
atomiccalls: which ismemory_order_seq_cst
From https://en.cppreference.com/w/cpp/atomic/memory_order:
memory_order_seq_cst: A load operation with this memory order performs an acquire operation, a store performs a release operation, and read-modify-write performs both an acquire operation and a release operation, plus a single total order exists in which all threads observe all modifications in the same order (see Sequentially-consistent ordering below)
Here are my questions:
- Is this implementation correct? (I have tested my reader/writer threading program against this implementation as well as std::shared_mutex. The behaviors I observed so far look consistent between the two implementations.)
- Is this implementation efficient? Specifically, I need help with the finer nuances on the various memory model flags. For simplicity, I have chosen to use the default
memory_order_seq_cstmemory model for allstd::atomiccommands. I am not sure if it would work but not at max efficiency, OR is it just plain wrong? - If no to 1) or 2), would you please help me correct my implementation? Please post an actual chunk of code in addition to word explanations. It would help me understand your explanation much better.
Thanks for your help and comments!
#include <atomic>
using namespace std;
class my_shared_mutex_cas
{
// -1: writer locked
// 0: not locked
// 1,2,3,... : reader locked
atomic<int> ref_count_;
static constexpr int WRITER_LOCKED = -1;
static constexpr int UNLOCKED = 0;
public:
my_shared_mutex_cas()
:ref_count_(0)
{}
// writer lock, blocking
void lock()
{
int lock_state;
do
{
lock_state = UNLOCKED; // can lock a writer lock only when ref_count == 0
} while (!ref_count_.compare_exchange_weak(lock_state, WRITER_LOCKED));
}
// writer unlock
void unlock()
{
ref_count_.store(UNLOCKED);
}
// writer lock, non-blocking
bool try_lock()
{
int lock_state = UNLOCKED;
return ref_count_.compare_exchange_weak(lock_state, WRITER_LOCKED);
}
// reader lock
void lock_shared()
{
int lock_state;
do
{
do
{
lock_state = ref_count_.load();
} while (lock_state == WRITER_LOCKED); // spin until write lock is released
} while (!ref_count_.compare_exchange_weak(lock_state, lock_state+1));
}
// reader unlock
void unlock_shared()
{
// decrement ref counter
ref_count_.fetch_sub(1);
}
// reader lock, non blocking
// if writer locked, return false right away
// otherwise try lock shared
bool try_lock_shared()
{
int lock_state = ref_count_.load();
if (lock_state == WRITER_LOCKED) return false;
// here lock_state >= 0
return ref_count_.compare_exchange_weak(lock_state, lock_state + 1);
}
};
Aucun commentaire:
Enregistrer un commentaire