Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement class lock objects for multithreaded access

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?

like image 848
user1709708 Avatar asked Nov 24 '15 13:11

user1709708


1 Answers

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.

like image 68
Angew is no longer proud of SO Avatar answered Oct 06 '22 06:10

Angew is no longer proud of SO