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