如何为多线程访问实现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 分钟开始。