c ++ 17中的scoped_lock看起来像是我所追求的功能,但是目前我与c ++ 11相关。
此刻,当我们多次使用同一个互斥锁调用guard_lock时,我看到死锁问题。 scoped_lock是否可以防止多次调用(即重新进入?)? 在c ++ 11 w / lock_guard中是否有最佳做法?
mutex lockingMutex;
void get(string s)
{
lock_guard<mutex> lock(lockingMutex);
if (isPresent(s))
{
//....
}
}
bool isPresent(string s)
{
bool ret = false;
lock_guard<mutex> lock(lockingMutex);
//....
return ret;
}
答案 0 :(得分:4)
要能够多次锁定同一个互斥锁,需要使用std::recursive_mutex。递归互斥比非递归互斥更为昂贵。
不过,最好的做法是设计代码,使线程不会多次锁定同一个互斥锁。例如,让您的公共函数首先锁定互斥锁,然后调用期望该互斥锁已被锁定的实现函数。实现函数不得调用锁定互斥体的公共API函数。例如:
class A {
std::mutex m_;
int state_ = 0;
private: // These expect the mutex to have been locked.
void foo_() {
++state_;
}
void bar_() {
this->foo_();
}
public: // Public functions lock the mutex first.
void foo() {
std::lock_guard<std::mutex> lock(m_);
this->foo_();
}
void bar() {
std::lock_guard<std::mutex> lock(m_);
this->bar_();
}
};
答案 1 :(得分:0)
作用域锁不能提供您正在寻找的功能。
作用域锁只是锁卫的杂种版本。它仅由于将锁保护更改为可变模板而存在一些ABI问题。
要具有可重入互斥体,您需要使用可重入互斥体。但是,这两者在运行时都比较昂贵,并且通常表明您的互斥状态下缺乏照顾。持有互斥锁时,您应该对正在执行的所有其他同步操作有完整的了解。
一旦完全了解正在执行的所有同步操作,就很容易避免递归锁定。
您可以在此处考虑两种模式。首先,将公共锁定API与私有非锁定API分开。第二,从实现中分离同步。
private:
mutex lockingMutex;
bool isPresent(string s, lock_guard<mutex> const& lock) {
bool ret = false;
//....
return ret;
}
void get(string s, lock_guard<mutex> const& lock) {
if (isPresent(s, lock))
{
//....
}
}
public:
void get(string s) {
return get( std::move(s), lock_guard<mutex>(lockingMutex) );
}
bool isPresent(string s) {
return isPresent( std::move(s), lock_guard<mutex>(lockingMutex) );
}
};
在这里,我将lock_guard<mutex>
用作“证明我们有锁”。
通常更好的选择是将您的类写为非线程安全的,然后使用包装器:
template<class T>
struct mutex_guarded {
template<class T0, class...Ts,
std::enable_if_t<!std::is_same<std::decay_t<T0>, mutex_guarded>{}, bool> =true
>
mutex_guarded(T0&&t0, Ts&&...ts):
t( std::forward<T0>(t0), std::forward<Ts>(ts)... )
{}
mutex_guarded()=default;
~mutex_guarded=default;
template<class F>
auto read( F&& f ) const {
auto l = lock();
return f(t);
}
template<class F>
auto write( F&& f ) {
auto l = lock();
return f(t);
}
private:
auto lock() { return std::unique_lock<std::mutex>(m); }
auto lock() const { return std::unique_lock<std::mutex>(m); }
mutable std::mutex m;
T t;
};
现在我们可以这样使用:
mutex_guarded<Foo> foo;
foo.write([&](auto&&foo){ foo.get("hello"); } );
您可以编写mutex_gaurded
,shared_mutex_guarded
,not_mutex_guarded
甚至async_guarded
(返回期货并在工作线程中序列化操作)。
只要该类在方法中不保留其自己的“控制区域”,此模式就可以使编写互斥保护的数据变得更加容易,并使您可以将相关的互斥保护的数据组合到一个包中,而不必重写它们。 / p>