由于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()
在你的旋转循环中,如果多个线程都试图同时获得锁,可能会有一些backoff来减少争用。
通常您希望在看到锁可用之前将其旋转为只读,而不是一直使用原子rmw。(参见cmpxchg是否在失败时写入目标缓存行?)如果没有,是不是比xchg更好呢?
另外,short
是一个奇怪的选择;如果任何大小的表现比int差,它通常是短的。但如果这能帮助它和你要修改的数据打包到同一条缓存行中,那就没问题了。(尽管该缓存行将成为其他线程试图获取锁的错误共享争用的受害者。)