Assuming I have the following oversimplified class and want to protect the resource from multi-thread access. How can I incorporate sth like a class-lock where each "entry-point" into the public interface first has to acquire a class lock before being allowed to use the interface?
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
}
I could do it by locking a class mutex in each of the methods and then have two versions of methods B-E like so:
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
}
// ...
But this actually looks more cumbersome and harder to get correct in larger, complicated interfaces than requiring the client to first ask the class for a lock object and then be allowed to savely use the class' interface until he releases the lock again. Is there a way to easily implement this? Can I just have a method getLock where I create a lock on a class mutex and use move-assigment to get it to the client? How can I ensure inside the class that the caller owns the lock when calling a method on it?
If you need your class to be thread-safe, that is, only usable under a lock, you can make all the public functions accept a reference to a std::lock
(ideally wrapped in a custom object, or at least a 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); }
};
However, an alternative would be to let the class ignore locking issues, and instead provide a thread-safe wrapper for it. A very similar idea was presented by Herb Sutter in one of his talks(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 from minute 36 on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With