use*_*708 6 c++ multithreading mutex locking
假设我有以下过于简化的类并希望保护资源免受多线程访问。我怎样才能像类锁一样合并某个类,其中每个“入口点”到公共接口中首先必须获得类锁,然后才能使用该接口?
class MyClass
{
public:
void A();
void B();
void C();
void D();
void E();
private:
SharedResource _res;
}
void MyClass::A()
{
B();
C();
D();
E();
}
void MyClass::B()
{
// do sth with _res
}
void MyClass::C()
{
// do sth with _res
}
void MyClass::D()
{
// do sth with _res
}
void MyClass::E()
{
// do sth with _res
}
Run Code Online (Sandbox Code Playgroud)
我可以通过在每个方法中锁定一个类互斥锁来实现,然后有两个版本的方法,如下所示:
void MyClass::A()
{
std::lock<std::mutex> lock(_mutex);
B_mtx();
C_mtx();
D_mtx();
E_mtx();
}
void MyClass::B()
{
std::lock<std::mutex> lock(_mutex);
B_mtx();
}
void MyClass::B_mtx()
{
// logic of B
}
// ...
Run Code Online (Sandbox Code Playgroud)
但这实际上在更大、更复杂的接口中看起来更麻烦,更难纠正,而不是要求客户端首先向类请求一个锁对象,然后被允许使用类的接口,直到他再次释放锁。有没有办法轻松实现这一点?我可以使用 getLock 方法在类互斥锁上创建锁并使用移动分配将其发送给客户端吗?如何在类内部调用方法时确保调用者拥有锁?
如果您需要您的类是线程安全的,即只能在锁下使用,您可以使所有公共函数接受对 a 的引用std::lock(理想情况下包装在自定义对象中,或者至少是 typedef 中):
class MyClass
{
mutable std::mutex mtx;
public:
using Lock = std::unique_lock<std::mutex>;
void A(Lock &l)
{
assert(l.mutex() == mtx);
// ...
}
void B(Lock &l)
{
assert(l.mutex() == mtx);
// ...
}
Lock getLock() const
{ return Lock(mtx); }
void releaseLock(Lock &&l) const
{ Lock l2 = std::move(l); }
};
Run Code Online (Sandbox Code Playgroud)
然而,另一种方法是让类忽略锁定问题,并为其提供线程安全的包装器。Herb Sutter 在他的一次演讲中提出了一个非常相似的想法(1):
class MyClass
{
public:
void A()
{
//...
}
void B()
{
//...
}
};
class MyClass_ThreadSafe
{
MyClass m;
std::mutex mtx;
public:
template <class Operation>
auto call(Operation o) -> decltype(o(m))
{
std::unique_lock l(mtx);
return o(m);
}
};
// Usage:
MyClass_ThreadSafe mc;
mc.call(
[](MyClass &c)
{
c.A();
c.B();
}
);
Run Code Online (Sandbox Code Playgroud)
(1) C++ 及未来 2012 — Herb Sutter:从第 36 分钟开始的 C++ 并发性。