如何为多线程访问实现class锁对象
How to implement class lock objects for multithreaded access
假设我有以下过度简化的 class 并且想要保护资源免受多线程访问。我怎样才能像 class-lock 那样合并某些东西,其中每个 "entry-point" 进入 public 接口首先必须在被允许使用接口之前获得 class 锁?
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
}
我可以通过在每个方法中锁定一个 class 互斥体来实现,然后有两个版本的方法 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
}
// ...
但这实际上看起来比要求客户端首先向 class 请求锁定对象然后被允许安全地使用 class' 界面,直到他再次释放锁。
有没有办法轻松实现这个?我可以只使用方法 getLock 在 class 互斥量上创建一个锁并使用 move-assigment 将其发送给客户端吗?我如何确保在 class 中调用方法时调用者拥有锁?
如果您需要您的class是线程安全的,也就是说,只能在锁下使用,您可以使所有public函数接受对 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); }
};
但是,另一种方法是让 class 忽略锁定问题,而是为其提供一个线程安全的包装器。 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 分钟开始。
假设我有以下过度简化的 class 并且想要保护资源免受多线程访问。我怎样才能像 class-lock 那样合并某些东西,其中每个 "entry-point" 进入 public 接口首先必须在被允许使用接口之前获得 class 锁?
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
}
我可以通过在每个方法中锁定一个 class 互斥体来实现,然后有两个版本的方法 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
}
// ...
但这实际上看起来比要求客户端首先向 class 请求锁定对象然后被允许安全地使用 class' 界面,直到他再次释放锁。 有没有办法轻松实现这个?我可以只使用方法 getLock 在 class 互斥量上创建一个锁并使用 move-assigment 将其发送给客户端吗?我如何确保在 class 中调用方法时调用者拥有锁?
如果您需要您的class是线程安全的,也就是说,只能在锁下使用,您可以使所有public函数接受对 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); }
};
但是,另一种方法是让 class 忽略锁定问题,而是为其提供一个线程安全的包装器。 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 分钟开始。