如何在调用其他使用 unique_lock 的函数的 class 中使用 unique_lock?
How to use unique_lock in a class that calls other functions that use unique_lock?
我有一个 class 需要使其成为线程安全的。我试图通过在 class 中的每个函数的顶部放置一个唯一的锁来做到这一点。问题是,一旦一个函数调用另一个函数(在这个 class 中),互斥锁似乎就互相锁定了,尽管它们在不同的函数中。我怎样才能阻止这种情况发生?
一个例子是带有 get() 和 set() 函数的 class,它们都在每个函数的开头使用 unique_lock。但是在 set() 中你想在某个时候调用 get(),但是没有 set() 的互斥锁锁定 get() 的互斥。但是,如果直接调用,get() 中的互斥体应该仍然有效。
std::recursive_mutex
就是你想要的。一个线程内可以多次加锁
如果你提供代码就更清楚了。
问题源于所有锁共享同一个互斥对象这一事实。递归锁可以在某种程度上解决问题。
但请记住,吸气剂不一定必须被锁定。
几年前我遇到了同样的问题,有几个线程在代理对象上协同工作。最后的解决方案是我必须定义多个互斥量。如果可能,请使用 Qt 信号或升压信号,它们中的每一个都可以帮助您找到更好的来回传递数据的解决方案。
虽然使用 std::recursive_mutex
会起作用,但它可能会产生一些可以避免的开销。
相反,在不获取锁但假定锁由当前执行线程持有的私有方法中实现所有逻辑。然后提供必要的 public 方法来获取锁并将调用转发给相应的私有方法。在私有方法的实现中,您可以自由调用其他私有方法,而不必担心多次锁定互斥量。
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()
{
/* ... */
}
};
请注意,这只是(可能)解决您的问题的另一种方法。
通过向所有操作添加互斥锁来制作 class "thead safe" 是代码味道。使用递归互斥体这样做更糟糕,因为它意味着缺乏对什么被锁定以及什么操作锁定的控制和理解。
虽然它通常允许一些有限的多线程访问,但经常会导致死锁、争用和性能下降。
基于锁的并发不安全组合,除了少数情况。你可以取两个正确的基于锁的 datastructures/algorithms,连接它们,最后得到 incorrect/unsafe 代码。
考虑让您的类型保持单线程,实现 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
可以变成 public 并且 vararg 值得做一些工作(处理赋值之类的事情)。
现在调用自己的方法没问题了。外部使用看起来像:
std::mutex_guarded<std::ostream&> safe_cout(std::cout);
safe_cout.write([&](auto& cout){ cout<<"hello "<<"world\n"; });
您还可以编写异步包装器(在线程池和 return 期货中执行任务)等。
我有一个 class 需要使其成为线程安全的。我试图通过在 class 中的每个函数的顶部放置一个唯一的锁来做到这一点。问题是,一旦一个函数调用另一个函数(在这个 class 中),互斥锁似乎就互相锁定了,尽管它们在不同的函数中。我怎样才能阻止这种情况发生?
一个例子是带有 get() 和 set() 函数的 class,它们都在每个函数的开头使用 unique_lock。但是在 set() 中你想在某个时候调用 get(),但是没有 set() 的互斥锁锁定 get() 的互斥。但是,如果直接调用,get() 中的互斥体应该仍然有效。
std::recursive_mutex
就是你想要的。一个线程内可以多次加锁
如果你提供代码就更清楚了。 问题源于所有锁共享同一个互斥对象这一事实。递归锁可以在某种程度上解决问题。 但请记住,吸气剂不一定必须被锁定。 几年前我遇到了同样的问题,有几个线程在代理对象上协同工作。最后的解决方案是我必须定义多个互斥量。如果可能,请使用 Qt 信号或升压信号,它们中的每一个都可以帮助您找到更好的来回传递数据的解决方案。
虽然使用 std::recursive_mutex
会起作用,但它可能会产生一些可以避免的开销。
相反,在不获取锁但假定锁由当前执行线程持有的私有方法中实现所有逻辑。然后提供必要的 public 方法来获取锁并将调用转发给相应的私有方法。在私有方法的实现中,您可以自由调用其他私有方法,而不必担心多次锁定互斥量。
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()
{
/* ... */
}
};
请注意,这只是(可能)解决您的问题的另一种方法。
通过向所有操作添加互斥锁来制作 class "thead safe" 是代码味道。使用递归互斥体这样做更糟糕,因为它意味着缺乏对什么被锁定以及什么操作锁定的控制和理解。
虽然它通常允许一些有限的多线程访问,但经常会导致死锁、争用和性能下降。
基于锁的并发不安全组合,除了少数情况。你可以取两个正确的基于锁的 datastructures/algorithms,连接它们,最后得到 incorrect/unsafe 代码。
考虑让您的类型保持单线程,实现 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
可以变成 public 并且 vararg 值得做一些工作(处理赋值之类的事情)。
现在调用自己的方法没问题了。外部使用看起来像:
std::mutex_guarded<std::ostream&> safe_cout(std::cout);
safe_cout.write([&](auto& cout){ cout<<"hello "<<"world\n"; });
您还可以编写异步包装器(在线程池和 return 期货中执行任务)等。