在设计线程安全类时,在嵌套调用的情况下避免死锁

时间:2017-01-01 16:04:28

标签: c++ multithreading

我知道我可以在类中使用mutex成员并将其锁定在每个方法中以防止数据在多线程环境中竞争。但是,如果类的方法中存在嵌套调用,则此方法可能会导致死锁,例如下面的类中的add_one()add_two()。对每种方法使用不同的mutex是一种解决方法。但是,在嵌套调用的情况下,是否有更原则和优雅的方法来防止死锁?

class AddClass {
public:
  AddClass& operator=(AddClass const&) = delete; // disable copy-assignment constructor
  AddClass(int val) : base(val) {}
  int add_one() { return ++base; }
  int add_two() { 
    add_one; 
    add_one;
    return base;
  }
private:
  int base;
};

4 个答案:

答案 0 :(得分:5)

完全出于此目的std::recursive_mutex

另一种避免递归互斥导致的开销的方法是将公共同步接口与私有非同步实现分开:

class AddClass {
public:
  AddClass& operator=(AddClass const&) = delete; // disable copy-assignment constructor
  AddClass(int val) : base(val) {}
  int add_one() {
     std::lock_guard<std::mutex> guard{mutex};
     return add_one_impl();
  }
  int add_two() {
     std::lock_guard<std::mutex> guard{mutex};
     return add_two_impl();
  }
private:
  int base;
  std::mutex mutex;
  int add_one_impl() { 
    return ++base;
  }
  int add_two_impl() { 
    add_one_impl(); 
    add_one_impl();
    return base;
  }
};

但请注意,这并不总是可行的。例如,如果你有一个接受回调的方法并在持有锁的同时调用它,则回调可能会尝试调用你的类的其他公共方法,并且你再次面临双重锁定尝试。

答案 1 :(得分:1)

递归互斥锁是一个可锁定的对象,就像互斥锁一样,但允许同一个线程获取互斥对象的多个所有权级别。

何时以及如何使用递归互斥锁 - 链接下面

Recursive Mutex

注意:递归和非递归互斥锁有不同的用例。

希望它有所帮助。

答案 2 :(得分:0)

对此的一般解决方案称为reentrant mutex

  

尝试在普通互斥锁上执行“锁定”操作   当互斥锁已经锁定时,(锁定)将失败或阻塞   一个递归的互斥锁,当且仅当这个操作成功时,这个操作才会成功   锁定线程是已经持有锁定的线程。通常,a   递归互斥锁跟踪它被锁定的次数,以及   需要在其他之前执行同样多的解锁操作   线程可以锁定它。

C ++ 11标准库中有一个:http://en.cppreference.com/w/cpp/thread/recursive_mutex

答案 3 :(得分:0)

使用锁定锁的函数实现公共接口,并调用执行工作的私有成员函数。私有函数可以相互调用,而不需要重新锁定互斥锁的开销,也不需要使用递归的互斥锁,这些互斥锁被许多人视为设计失败的标志。