shared_lock 用 std::atomic 实现
shared_lock implemented with std::atomic
我无法承受 shared_lock 因为它太重了。相反,我实现了我认为是 shared_lock 但具有原子值的东西。此代码是否有效,还是我遗漏了什么?
更新:我在原子互斥锁周围添加了 RAII 类。
namespace atm {
static const unsigned int g_unlocked = 0;
static const unsigned int g_exclusive = std::numeric_limits<unsigned int>::max();
using mutex_type = std::atomic<unsigned int>;
class exclusive_lock {
public:
exclusive_lock(mutex_type& mutex) : _mutex(mutex) {
unsigned int expected = g_unlocked;
while (!_mutex.compare_exchange_weak(expected, g_exclusive)) {
_mm_pause();
expected = g_unlocked;
}
}
~exclusive_lock() { _mutex.store(g_unlocked, std::memory_order_release); }
private:
mutex_type& _mutex;
};
class shared_lock {
public:
shared_lock(mutex_type& mutex) : _mutex(mutex) {
unsigned int expected = _mutex;
while (expected == g_exclusive || !_mutex.compare_exchange_weak(expected, expected + 1)) {
_mm_pause();
expected = _mutex;
}
}
~shared_lock() { _mutex.fetch_sub(1, std::memory_order_release); }
private:
mutex_type& _mutex;
};
} // namespace atm
为了正确性,我认为这看起来很合理,我看不出有什么问题。我可能漏掉了什么,但是 seq_cst CAS 足以获取锁。看起来您通过将最大值用作特殊值来避免整数回绕。
mutex=0
解锁只需要release
,不需要seq_cst
。 (与 -=1
共享解锁相同,但这不会使其在 x86 上更高效,仅在弱排序的 ISA 上)。另外, compare_exchange_weak
完全没问题;无论如何,您正在循环重试,因此虚假失败与失败的比较没有什么不同。
如果您使用的是 x86,您通常希望在自旋循环中 _mm_pause()
,如果多个线程都试图同时获取锁,则可能需要某种回退来减少争用。
并且通常您希望旋转只读直到您看到可用的锁,而不是继续使用原子 RMW。 (参见 )。
此外,short
是一个奇怪的选择;如果任何尺寸的性能都比 int 差,那么它通常很短。但可能没问题,好吧,我想这是否有助于将它打包到与您正在修改的数据相同的缓存行中。 (尽管该缓存行将成为其他线程攻击它试图获取锁的错误共享争用的受害者。)
我无法承受 shared_lock 因为它太重了。相反,我实现了我认为是 shared_lock 但具有原子值的东西。此代码是否有效,还是我遗漏了什么?
更新:我在原子互斥锁周围添加了 RAII 类。
namespace atm {
static const unsigned int g_unlocked = 0;
static const unsigned int g_exclusive = std::numeric_limits<unsigned int>::max();
using mutex_type = std::atomic<unsigned int>;
class exclusive_lock {
public:
exclusive_lock(mutex_type& mutex) : _mutex(mutex) {
unsigned int expected = g_unlocked;
while (!_mutex.compare_exchange_weak(expected, g_exclusive)) {
_mm_pause();
expected = g_unlocked;
}
}
~exclusive_lock() { _mutex.store(g_unlocked, std::memory_order_release); }
private:
mutex_type& _mutex;
};
class shared_lock {
public:
shared_lock(mutex_type& mutex) : _mutex(mutex) {
unsigned int expected = _mutex;
while (expected == g_exclusive || !_mutex.compare_exchange_weak(expected, expected + 1)) {
_mm_pause();
expected = _mutex;
}
}
~shared_lock() { _mutex.fetch_sub(1, std::memory_order_release); }
private:
mutex_type& _mutex;
};
} // namespace atm
为了正确性,我认为这看起来很合理,我看不出有什么问题。我可能漏掉了什么,但是 seq_cst CAS 足以获取锁。看起来您通过将最大值用作特殊值来避免整数回绕。
mutex=0
解锁只需要release
,不需要seq_cst
。 (与 -=1
共享解锁相同,但这不会使其在 x86 上更高效,仅在弱排序的 ISA 上)。另外, compare_exchange_weak
完全没问题;无论如何,您正在循环重试,因此虚假失败与失败的比较没有什么不同。
如果您使用的是 x86,您通常希望在自旋循环中 _mm_pause()
,如果多个线程都试图同时获取锁,则可能需要某种回退来减少争用。
并且通常您希望旋转只读直到您看到可用的锁,而不是继续使用原子 RMW。 (参见
此外,short
是一个奇怪的选择;如果任何尺寸的性能都比 int 差,那么它通常很短。但可能没问题,好吧,我想这是否有助于将它打包到与您正在修改的数据相同的缓存行中。 (尽管该缓存行将成为其他线程攻击它试图获取锁的错误共享争用的受害者。)