假设我有以下过度简化的类,并希望保护资源免受多线程访问。我怎样才能将类似于类锁的方法整合到公共接口中的每个“入口点”首先必须获得类锁定才能被允许使用该接口?
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
}
我可以通过在每个方法中锁定一个类互斥来实现它,然后有两个版本的方法B-E,如下所示:
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
}
// ...
但这实际上看起来更麻烦,更难以在更大,更复杂的接口中获得正确,而不是要求客户端首先向类请求锁定对象,然后允许它保存使用类的接口,直到他再次释放锁定。 有没有办法轻松实现这个?我可以使用方法getLock,在类互斥锁上创建锁并使用move-assigment将其传递给客户端吗?在调用方法时,如何在类中确保调用者拥有锁?
答案 0 :(得分:2)
如果您需要您的类是线程安全的,也就是说,只能在锁定下使用,您可以使所有公共函数接受对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); }
};
但是,另一种方法是让类忽略锁定问题,而是为它提供一个线程安全的包装器。 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();
}
);
(1) C++ and Beyond 2012 — Herb Sutter: C++ Concurrency从第36分钟开始。