我有一个我需要使用线程安全的类。我试图通过在类中的每个函数的顶部放置一个独特的锁来做到这一点。问题是,只要一个函数调用另一个函数(在此类中),互斥锁似乎彼此锁定,尽管它们处于不同的函数中。我怎样才能阻止这种情况发生?
一个例子是一个带有get()和set()函数的类,它们在每个函数的开头都使用unique_lock。但是在set()中你想在某个时候调用get(),但是没有set()的互斥锁定get()的互斥锁。但是,如果直接调用,get()中的互斥锁仍然可以工作。
答案 0 :(得分:5)
通过向所有操作添加互斥锁使类“thead safe”是代码味道。使用递归互斥体这样做更糟糕,因为它意味着缺乏对锁定内容和操作锁定的控制和理解。
虽然它通常允许一些有限的多线程访问,但经常导致死锁,争用和性能下降。
基于锁定的并发无法安全地撰写,但在有限的情况下除外。您可以采用两个正确的基于锁的数据结构/算法,连接它们,最后得到错误/不安全的代码。
考虑将您的类型保留为单线程,实现const
方法,这些方法可以在不同步的情况下相互调用,然后使用不可变实例和外部同步实例的混合。
template<class T>
struct mutex_guarded {
template<class F>
auto read( F&& f ) const {
return access( std::forward<F>(f), *this );
}
template<class F>
auto write( F&& f ) {
return access( std::forward<F>(f), *this );
}
mutex_guarded()=default;
template<class T0, class...Ts,
std::enable_if_t<!std::is_same<mutex_guarded, std::decay_t<T0>>, bool> =true
>
mutex_guarded(T0&&t0, Ts&&ts):
t(std::forward<T0>(t0),std::forward<Ts>(ts)...)
{}
private:
template<class F, class Self>
friend auto access(F&& f, Self& self ){
auto l = self.lock();
return std::forward<F>(f)( self.t );
}
mutable std::mutex m;
T t;
auto lock() const { return std::unique_lock<std::mutex>(m); }
};
和共享互斥的类似(它有两个lock
重载)。 access
可以公开,vararg也可以做一些工作(处理分配等事情)。
现在调用自己的方法没问题。外部使用看起来像:
std::mutex_guarded<std::ostream&> safe_cout(std::cout);
safe_cout.write([&](auto& cout){ cout<<"hello "<<"world\n"; });
你也可以编写异步包装器(在线程池中执行任务并返回期货)等。
答案 1 :(得分:1)
std::recursive_mutex
就是你想要的。它可以在一个线程中锁定多次。
答案 2 :(得分:0)
如果您提供代码,可能会更清楚。 问题产生于所有锁共享相同的互斥对象。递归锁可以在某种程度上解决问题。 但请记住,吸气剂不一定要锁定。 几年前我遇到了同样的问题,有几个线程在代理对象上一起工作。最终的解决方案是我必须定义多个互斥锁。如果可能,请使用Qt信号或增强信号,每个信号都可以帮助您提供更好的来回传递数据的解决方案。
答案 3 :(得分:0)
虽然使用std::recursive_mutex
会起作用,但可能会产生一些可以避免的开销。
相反,在私有方法中实现所有逻辑,这些逻辑不接受锁定,但假设锁定由当前执行线程保存。然后提供必要的公共方法,将锁定并将调用转发给相应的私有方法。在私有方法的实现中,您可以自由调用其他私有方法,而无需担心多次锁定互斥锁。
struct Widget
{
void foo()
{
std::unique_lock<std::mutex> lock{ m };
foo_impl();
}
void bar()
{
std::unique_lock<std::mutex> lock{ m };
bar_impl();
}
private:
std::mutex m;
void foo_impl()
{
bar_impl(); // Call other private methods
}
void bar_impl()
{
/* ... */
}
};
请注意,这只是(可能)解决问题的另一种方法。