c – 如何为多线程访问实现类锁对象

假设我有以下过度简化的类,并希望保护资源免受多线程访问.我怎样才能将类似于类锁的方法整合到公共接口中的每个“入口点”首先必须获取类锁定才允许使用该接口?

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
}

我可以通过在每个方法中锁定一个类互斥,然后有两个版本的方法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
}

// ...

但这实际上看起来更麻烦,更难以在更大,更复杂的接口中获得正确,而不是要求客户端首先向类请求锁定对象,然后允许它保存使用类的接口,直到他再次释放锁定.
有没有办法轻松实现这个?我可以使用方法getLock,在类互斥锁上创建锁并使用move-assigment将其传递给客户端吗?在调用方法时,如何在类中确保调用者拥有锁?

最佳答案 如果你需要你的类是线程安全的,也就是说,只能在一个锁下使用,你可以让所有公共函数接受对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); }
};

但是,另一种方法是让类忽略锁定问题,而是为它提供一个线程安全的包装器. 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)从36分钟开始C++ and Beyond 2012 — Herb Sutter: C++ Concurrency.

点赞