在不同的函数中锁定/解锁 std::unique_lock
Posted
技术标签:
【中文标题】在不同的函数中锁定/解锁 std::unique_lock【英文标题】:Lock/unlock std::unique_lock in different functions 【发布时间】:2021-11-19 16:38:01 【问题描述】:我正在编写一个使用条件变量的程序:
bool flag;
std::mutex mtx;
std::condition_variable cond;
std::unique_lock<std::mutex> lock(mtx);
cond.wait(lock, [&] return flag; );
// ... do some stuff
// ... do some more stuff
flag = false;
// lock unlocked here
现在我面临的问题是“做一些事情”和“做更多事情”实际上是在两个单独的回调函数中实现的,这些函数从其他地方一个接一个地调用,即:
void callback1()
std::unique_lock<std::mutex> lock(mtx);
cond.wait(lock, [&] return flag; );
// ... do some stuff
// ERROR! lock unlocked too early
void callback2()
// ... do some more stuff
flag = false;
// lock should be unlocked here
我该如何解决这个问题?我可以将这两个回调封装在某个将锁作为成员变量保存的对象中,但在我看来,这违反了std::unique_lock
的预期语义。
编辑:更多上下文:
其他人编写了一个类Foo
,如下所示:
class Foo
public:
void bar()
before_bar_callback();
// ...
after_bar_callback();
private:
virtual void before_bar_callback()
virtual void after_bar_callback()
;
我无法修改Foo::bar
,它在单独的线程中运行。我想等待before_bar_callback
中的条件变量,然后设置flag = false
并解锁after_bar_callback
中的关联锁。
【问题讨论】:
这些信息不足以得出一个平衡的答案。为什么是 2 个回调而不是 1 个。如果他们如此依赖,我认为他们应该合二为一。 @PKramer 我添加了更多上下文。 我想我知道你在做什么。如果我理解正确,您希望两个回调基本上都是原子的,因为您不想冒两者之间“无效”的风险。我参与了一个处理这个问题的项目,他们最终决定在回调之间解锁是可以的。否则你最终会陷入各种奇怪的死锁场景。现实情况是该程序的多线程并不足以导致错误(但它确实发生了)。在此之后,我认为唯一的解决方案是使用异步事件,这会带来其自身的挑战。 使用那个分割界面,你不能使用 RAII :/ 您可以使用std::condition_variable_any
并将mtx
直接传递给它的wait
。这样callback1
返回时不会自动解锁。
【参考方案1】:
根据您的信息,这是我最接近一种 RAII 方法的方法。 当一个类没有做你想做的事时,有时包装它会有所帮助。 这就是我能想到的。理想情况下,您仍然应该检查 Foo 是否仍不能被“所有者”修改。
想法:在 Foo 有机会完成它的工作并调用你的回调之前获取一个锁。这样你就知道锁在你的回调中是活跃的。
#include <functional>
#include <mutex>
#include <iostream>
#include <condition_variable>
//-------------------------------------------------------------------------
// your stand in for Foo,
// the class you cannot change
class Foo
public:
Foo() = default;
virtual ~Foo() = default;
void bar()
// todo construct a context here
before_callback();
// work here
after_callback();
// and destroy it here.
private:
virtual void before_callback() ;
virtual void after_callback() ;
;
//-------------------------------------------------------------------------
// A class that should look like Foo but wraps it
// Foo is a member and FooWrapper can intercept calls
// to Foo and do extra things.
class MyFoo :
protected Foo
public:
MyFoo() = default;
virtual ~MyFoo() = default;
// shadows base classes bar intentionally
void bar()
std::unique_lock<std::mutex> lock m_mtx ;
m_lock = &lock;
Foo::bar();
;
void before_callback() override
//lock is active
m_cv.wait(*m_lock, [this] return m_flag; );
std::cout << "before_callback" << std::endl;
void after_callback() override
//lock is active
std::cout << "after_callback" << std::endl;
private:
bool m_flag true ;
std::condition_variable m_cv;
std::unique_lock<std::mutex>* m_lock nullptr ;
std::mutex m_mtx;
;
int main()
MyFoo foo;
foo.bar();
return 0;
【讨论】:
据我所知,Foo
是一个“接口”,因此无法包装它。然后你必须正常使用继承。 (如果你把它包装起来before_callback
/after_callback
无论如何都是无关紧要的,因为在调用m_foo.bar()
之前可能会获得锁)。
@Jarod42 接口可以是包装器,我同意回调不必在包装器中,只要它们可以访问锁的(指针)即可。无论如何,我真的不喜欢依赖解决方法(包括这个)。多年来我了解到,解决真正的问题(设计)会更好,即使这意味着要去找我使用的代码的供应商。
我的意思是,正如代码所示,我知道它应该有一个您无法更改的lib_function(Foo&);
(如Foo
)。
我现在看到 OP 暗示必须覆盖 before_callback/after_callback(即使他将虚拟函数设为私有)。然后一个虚拟派生类可以覆盖这些方法,并且仍然提供一个新的 bar 实现,该实现锁定并转发到 Foo::bar()。但是是的,如果它是一个 lib_function(Foo&) 事情看起来就不那么好了
@PKramer 是的,覆盖是想法(并且仍然适用于私有成员函数)。以上是关于在不同的函数中锁定/解锁 std::unique_lock的主要内容,如果未能解决你的问题,请参考以下文章
C++ 使用带有基类的已删除函数 std::unique_ptr