为C ++ 17计划了一个shared_mutex
课程。并且shared_timed_mutex
已经在C ++ 14中了。 (谁知道他们为什么按顺序排列,但无论如何。)然后从C ++ 11开始有一个recursive_mutex
和一个recursive_timed_mutex
。我需要的是shared_recursive_mutex
。我是否错过了标准中的某些内容,还是需要再等三年才能获得标准化版本?
如果目前没有这样的设施,那么仅使用标准C ++的这种功能的简单(第一优先级)和有效(第二优先级)实现是什么?
答案 0 :(得分:7)
递归属性与术语所有者一起运行,如果 shared_mutex 没有明确定义:多个线程可能{ {1}}同时打电话。
假设所有者作为调用.lock_shared()
(而非.lock()
!)的线程,递归共享互斥锁的实现可以简单地从.lock_shared()
派生:
shared_mutex
字段class shared_recursive_mutex: public shared_mutex
{
public:
void lock(void) {
std::thread::id this_id = std::this_thread::get_id();
if(owner == this_id) {
// recursive locking
count++;
}
else {
// normal locking
shared_mutex::lock();
owner = this_id;
count = 1;
}
}
void unlock(void) {
if(count > 1) {
// recursive unlocking
count--;
}
else {
// normal unlocking
owner = std::thread::id();
count = 0;
shared_mutex::unlock();
}
}
private:
std::atomic<std::thread::id> owner;
int count;
};
需要声明为原子,因为在.owner
方法中,它会在没有并发访问保护的情况下进行检查。
如果您想以递归方式调用.lock()
方法,则需要维护所有者地图,并且应使用其他互斥锁保护对该地图的访问。
允许活动.lock_shared()
的帖子调用.lock()
会使实施变得更加复杂。
最后,允许线程提前从.lock_shared()
锁定到.lock_shared()
禁止,因为它会导致两个线程可能出现死锁试图进行推进。
同样,递归 共享互斥锁的语义会非常脆弱,所以最好不要使用它。
答案 1 :(得分:4)
如果您使用的是Linux / POSIX平台,那么您很幸运,因为C ++互斥体是在POSIX之后建模的。 POSIX提供了更多功能,包括递归,进程共享等。将POSIX原语包装到C ++类中是很简单的。
答案 2 :(得分:3)
这是围绕类型T的快速线程安全包装器:
template<class T, class Lock>
struct lock_guarded {
Lock l;
T* t;
T* operator->()&&{ return t; }
template<class Arg>
auto operator[](Arg&&arg)&&
-> decltype(std::declval<T&>()[std::declval<Arg>()])
{
return (*t)[std::forward<Arg>(arg)];
}
T& operator*()&&{ return *t; }
};
constexpr struct emplace_t {} emplace {};
template<class T>
struct mutex_guarded {
lock_guarded<T, std::unique_lock<std::mutex>>
get_locked() {
return {{m},&t};
}
lock_guarded<T const, std::unique_lock<std::mutex>>
get_locked() const {
return {{m},&t};
}
lock_guarded<T, std::unique_lock<std::mutex>>
operator->() {
return get_locked();
}
lock_guarded<T const, std::unique_lock<std::mutex>>
operator->() const {
return get_locked();
}
template<class F>
std::result_of_t<F(T&)>
operator->*(F&& f) {
return std::forward<F>(f)(*get_locked());
}
template<class F>
std::result_of_t<F(T const&)>
operator->*(F&& f) const {
return std::forward<F>(f)(*get_locked());
}
template<class...Args>
mutex_guarded(emplace_t, Args&&...args):
t(std::forward<Args>(args)...)
{}
mutex_guarded(mutex_guarded&& o):
t( std::move(*o.get_locked()) )
{}
mutex_guarded(mutex_guarded const& o):
t( *o.get_locked() )
{}
mutex_guarded() = default;
~mutex_guarded() = default;
mutex_guarded& operator=(mutex_guarded&& o)
{
T tmp = std::move(o.get_locked());
*get_locked() = std::move(tmp);
return *this;
}
mutex_guarded& operator=(mutex_guarded const& o):
{
T tmp = o.get_locked();
*get_locked() = std::move(tmp);
return *this;
}
private:
std::mutex m;
T t;
};
您可以使用:
mutex_guarded<std::vector<int>> guarded;
auto s0 = guarded->size();
auto s1 = guarded->*[](auto&&e){return e.size();};
两者都做了大致相同的事情,并且仅在互斥锁被锁定时才访问被保护对象。
从@tsyvarev的回答中窃取(稍作修改)我们得到:
class shared_recursive_mutex
{
std::shared_mutex m
public:
void lock(void) {
std::thread::id this_id = std::this_thread::get_id();
if(owner == this_id) {
// recursive locking
++count;
} else {
// normal locking
m.lock();
owner = this_id;
count = 1;
}
}
void unlock(void) {
if(count > 1) {
// recursive unlocking
count--;
} else {
// normal unlocking
owner = std::thread::id();
count = 0;
m.unlock();
}
}
void lock_shared() {
std::thread::id this_id = std::this_thread::get_id();
if (shared_counts->count(this_id)) {
++(shared_count.get_locked()[this_id]);
} else {
m.lock_shared();
shared_count.get_locked()[this_id] = 1;
}
}
void unlock_shared() {
std::thread::id this_id = std::this_thread::get_id();
auto it = shared_count->find(this_id);
if (it->second > 1) {
--(it->second);
} else {
shared_count->erase(it);
m.unlock_shared();
}
}
private:
std::atomic<std::thread::id> owner;
std::atomic<std::size_t> count;
mutex_guarded<std::map<std::thread::id, std::size_t>> shared_counts;
};
try_lock
和try_lock_shared
作为练习。
锁定和解锁两者共享锁定互斥锁两次(这是安全的,因为分支实际上是关于&#34;这个线程是否控制互斥锁&#34;,而另一个线程无法改变来自&#34的答案;没有&#34;到&#34;是&#34;反之亦然)。您可以使用->*
而不是->
进行一次锁定,这样可以加快速度(以逻辑中的某些复杂性为代价)。
以上不支持拥有独占锁,然后是共享锁。这很棘手。它不支持拥有共享锁,然后升级到一个唯一的锁,因为当2个线程尝试时,基本上不可能阻止它死锁。
最后一个问题可能就是为什么递归共享互斥锁是一个坏主意。
答案 3 :(得分:2)
可以使用现有基元构造共享递归互斥锁。不过,我不建议这样做。
这并不简单,并且包装现有的POSIX实现(或者您的平台的任何原生实现)很可能更有效。
如果您做决定编写自己的实现,那么使其高效仍然取决于特定于平台的详细信息,因此您要么为每个平台编写具有不同实现的接口,要么您正在选择一个平台,而且可以轻松地使用原生(POSIX或其他)设施。
我当然不打算提供示例递归读/写锁实现,因为它对于Stack Overflow答案来说是一个完全不合理的工作量。
答案 4 :(得分:0)
分享我的实施,没有承诺
recursive_shared_mutex.h
#ifndef _RECURSIVE_SHARED_MUTEX_H
#define _RECURSIVE_SHARED_MUTEX_H
#include <thread>
#include <mutex>
#include <map>
struct recursive_shared_mutex
{
public:
recursive_shared_mutex() :
m_mtx{}, m_exclusive_thread_id{}, m_exclusive_count{ 0 }, m_shared_locks{}
{}
void lock();
bool try_lock();
void unlock();
void lock_shared();
bool try_lock_shared();
void unlock_shared();
recursive_shared_mutex(const recursive_shared_mutex&) = delete;
recursive_shared_mutex& operator=(const recursive_shared_mutex&) = delete;
private:
inline bool is_exclusive_locked()
{
return m_exclusive_count > 0;
}
inline bool is_shared_locked()
{
return m_shared_locks.size() > 0;
}
inline bool can_exclusively_lock()
{
return can_start_exclusive_lock() || can_increment_exclusive_lock();
}
inline bool can_start_exclusive_lock()
{
return !is_exclusive_locked() && (!is_shared_locked() || is_shared_locked_only_on_this_thread());
}
inline bool can_increment_exclusive_lock()
{
return is_exclusive_locked_on_this_thread();
}
inline bool can_lock_shared()
{
return !is_exclusive_locked() || is_exclusive_locked_on_this_thread();
}
inline bool is_shared_locked_only_on_this_thread()
{
return is_shared_locked_only_on_thread(std::this_thread::get_id());
}
inline bool is_shared_locked_only_on_thread(std::thread::id id)
{
return m_shared_locks.size() == 1 && m_shared_locks.find(id) != m_shared_locks.end();
}
inline bool is_exclusive_locked_on_this_thread()
{
return is_exclusive_locked_on_thread(std::this_thread::get_id());
}
inline bool is_exclusive_locked_on_thread(std::thread::id id)
{
return m_exclusive_count > 0 && m_exclusive_thread_id == id;
}
inline void start_exclusive_lock()
{
m_exclusive_thread_id = std::this_thread::get_id();
m_exclusive_count++;
}
inline void increment_exclusive_lock()
{
m_exclusive_count++;
}
inline void decrement_exclusive_lock()
{
if (m_exclusive_count == 0)
{
throw std::logic_error("Not exclusively locked, cannot exclusively unlock");
}
if (m_exclusive_thread_id == std::this_thread::get_id())
{
m_exclusive_count--;
}
else
{
throw std::logic_error("Calling exclusively unlock from the wrong thread");
}
}
inline void increment_shared_lock()
{
increment_shared_lock(std::this_thread::get_id());
}
inline void increment_shared_lock(std::thread::id id)
{
if (m_shared_locks.find(id) == m_shared_locks.end())
{
m_shared_locks[id] = 1;
}
else
{
m_shared_locks[id] += 1;
}
}
inline void decrement_shared_lock()
{
decrement_shared_lock(std::this_thread::get_id());
}
inline void decrement_shared_lock(std::thread::id id)
{
if (m_shared_locks.size() == 0)
{
throw std::logic_error("Not shared locked, cannot shared unlock");
}
if (m_shared_locks.find(id) == m_shared_locks.end())
{
throw std::logic_error("Calling shared unlock from the wrong thread");
}
else
{
if (m_shared_locks[id] == 1)
{
m_shared_locks.erase(id);
}
else
{
m_shared_locks[id] -= 1;
}
}
}
std::mutex m_mtx;
std::thread::id m_exclusive_thread_id;
size_t m_exclusive_count;
std::map<std::thread::id, size_t> m_shared_locks;
std::condition_variable m_cond_var;
};
#endif
recursive_shared_mutex.cpp
#include "recursive_shared_mutex.h"
#include <condition_variable>
void recursive_shared_mutex::lock()
{
std::unique_lock sync_lock(m_mtx);
m_cond_var.wait(sync_lock, [this] { return can_exclusively_lock(); });
if (is_exclusive_locked_on_this_thread())
{
increment_exclusive_lock();
}
else
{
start_exclusive_lock();
}
}
bool recursive_shared_mutex::try_lock()
{
std::unique_lock sync_lock(m_mtx);
if (can_increment_exclusive_lock())
{
increment_exclusive_lock();
return true;
}
if (can_start_exclusive_lock())
{
start_exclusive_lock();
return true;
}
return false;
}
void recursive_shared_mutex::unlock()
{
{
std::unique_lock sync_lock(m_mtx);
decrement_exclusive_lock();
}
m_cond_var.notify_all();
}
void recursive_shared_mutex::lock_shared()
{
std::unique_lock sync_lock(m_mtx);
m_cond_var.wait(sync_lock, [this] { return can_lock_shared(); });
increment_shared_lock();
}
bool recursive_shared_mutex::try_lock_shared()
{
std::unique_lock sync_lock(m_mtx);
if (can_lock_shared())
{
increment_shared_lock();
return true;
}
return false;
}
void recursive_shared_mutex::unlock_shared()
{
{
std::unique_lock sync_lock(m_mtx);
decrement_shared_lock();
}
m_cond_var.notify_all();
}
如果线程拥有共享锁,那么它也可能会获得排他锁而不会放弃其共享锁。 (这当然不需要其他线程当前拥有共享锁或排他锁)
反之亦然,拥有排他锁的线程可能会获得共享锁。
有趣的是,这些属性还允许锁可以升级/降级。
临时升级锁:
recusrive_shared_mutex mtx;
foo bar;
mtx.lock_shared();
if (bar.read() == x)
{
mtx.lock();
bar.write(y);
mtx.unlock();
}
mtx.unlock_shared();
从排他锁降级为共享锁
recusrive_shared_mutex mtx;
foo bar;
mtx.lock();
bar.write(x);
mtx.lock_shared();
mtx.unlock();
while (bar.read() != y)
{
// Something
}
mtx.unlock_shared();