带有对象的互斥锁

Posted

技术标签:

【中文标题】带有对象的互斥锁【英文标题】:mutexes with objects 【发布时间】:2012-02-14 17:23:01 【问题描述】:

我试图了解如何在 c++ 中将互斥锁与对象一起使用。我有以下(琐碎的)多线程代码用作速度测试:

 struct Rope
   int n, steps, offset;
   //std::mutex mut;

   Rope() 
   Rope(int n, int steps, int offset) : n(n), steps(steps), offset(offset) 

   void compute()
     double a[n];
     for (int i=0; i<n; i++)
       a[i] = i + offset;
     for (int step=0; step<steps; step++)
       for (int i=0; i<n; i++)
     a[i] = sin(a[i]);
   

 ;

 void runTest()
   int numRuns = 30;
   int n = 10000;
   int steps = 10000;

   std::vector<Rope> ropes;
   std::vector<std::thread> threads;
   for (int i=0; i<numRuns; i++)
     ropes.push_back(Rope(n, steps, i));
   for (auto& r : ropes)
     threads.push_back(std::thread(&Rope::compute, r));
   for (std::thread& t : threads)
     t.join();
     

代码按原样运行良好,并且在我的 4 核机器上实现了约 4 倍的加速。当然,我没有在绳索中存储任何东西,所以不需要互斥锁。如果我现在假设我确实有一些需要保护的数据,我想将一个互斥锁附加到 Rope 并(例如)在 compute() 循环中调用 std::lock_guard 。但是,如果我取消注释互斥锁,我会收到一堆关于赋值和复制运算符“使用已删除函数”的编译器错误。我在安全锁定对象的目标中缺少什么?

【问题讨论】:

【参考方案1】:

使类线程安全的直接方法是添加互斥体属性并在访问器方法中锁定互斥体

class cMyClass 
  boost::mutex myMutex;
  cSomeClass A;
public:
  cSomeClass getA() 
    boost::mutex::scoped_lock lock( myMutex );
    return A;
  
;

问题是这使得类不可复制。这很重要,尤其是当您想将类的对象存储在容器中时。

我可以通过将互斥体设为静态来使事情正常进行。

class cMyClass 
  static boost::mutex myMutex;
  cSomeClass A;
public:
  cSomeClass getA() 
    boost::mutex::scoped_lock lock( myMutex );
    return A;
  
;

但是,这意味着当访问任何其他实例时,类的每个实例都会阻塞,因为它们都共享相同的互斥体。

理论上,包含非静态互斥锁的类可以通过手动编码复制构造函数和赋值运算符来复制,从而省去互斥锁。然而,正确地做到这一点既困难又乏味,尤其是对于具有大量在开发过程中经常更改的属性的类。

如果一个静态互斥锁在一个被阻塞时阻止对类的所有实例的访问是不可接受的,那么最好和最简单的方法是在类外部维护互斥锁。以这种方式暴露一个类的内部工作似乎很不幸,但替代方案要复杂得多,因此不可靠,而且当互斥体在访问类的代码级别处理时,我经常发现显着的优化。

【讨论】:

@MikeSeymour 除非我误解了他在说什么,否则他需要在所有线程中使用 same 互斥锁。使用移动语义将其放入容器中是行不通的。 (另一方面,正如目前所写的那样,每个线程都有自己的Rope 对象,因此不需要互斥锁。当问题没有得到明确解释时,很难说正确的解决方案是什么。)跨度> @Mike Seymour:std::mutex 不可移动。在每个 Rope 构造函数中默认构造 std::mutex 可以使 Rope 可移动或可复制。或者按照詹姆斯的建议使互斥锁成为静态的。这两种方法中哪一种合适,目前只有 OP 知道。 我的错;我应该在发布之前检查我的假设。【参考方案2】:

您错过了mutex 不可复制的事实。这意味着什么 制作互斥体的副本?获取和释放互斥锁的地方 位于Rope::compute,并且由于所有线程都必须访问 相同的互斥锁,您必须在runTest 中定义它并通过 引用或指针。

【讨论】:

以上是关于带有对象的互斥锁的主要内容,如果未能解决你的问题,请参考以下文章

Linux互斥锁

为啥我不能将带有互斥锁的仿函数传递给线程?

互斥锁 - 可以通过合并构建集合

互斥锁 & 共享锁

多线程安全----同步锁(互斥锁)

synchronized互斥锁实例解析