锁定构造函数和析构函数中的共享资源

Locking shared resources in constructor and destructor

本文关键字:共享资源 析构函数 构造函数 锁定      更新时间:2023-10-16

我相信我至少掌握了C++中多线程的基本知识,但我从来没有得到关于在构造函数或析构函数中围绕共享资源锁定互斥锁的明确答案。我觉得你应该把这两个地方都锁起来,但最近同事们不同意。假设以下类由多个线程访问:

class TestClass
{
public:
   TestClass(const float input) :
      mMutex(),
      mValueOne(1),
      mValueTwo("Text")
   {
      //**Does the mutex need to be locked here?
      mValueTwo.Set(input);
      mValueOne = mValueTwo.Get();
   }
   ~TestClass() 
   { 
     //Lock Here?
   }
   int GetValueOne() const
   {
      Lock(mMutex);
      return mValueOne;
   }
   void SetValueOne(const int value)
   {
      Lock(mMutex);
      mValueOne = value;
   }
   CustomType GetValueTwo() const
   {
      Lock(mMutex);
      return mValueOne;
   }
   void SetValueTwo(const CustomType type)
   {
      Lock(mMutex);
      mValueTwo = type;
   }
private:
   Mutex mMutex;
   int mValueOne;
   CustomType mValueTwo;
};

当然,通过初始化列表,一切都应该是安全的,但是构造函数中的语句呢?在析构函数中,进行非作用域锁定而从不解锁(本质上只调用pthread_mutex_destroy)是否有益?

多个线程不能构造同一个对象,也不应允许任何线程在完全构造对象之前使用该对象。因此,在健全的代码中,没有锁定的构造是安全的。

破坏是一个稍微困难的情况。但是,对对象进行适当的生命周期管理可以确保在某些线程可能仍在使用某个对象时,该对象永远不会被破坏

共享指针可以帮助实现这一点,例如:

  • 在某个线程中构造对象
  • 将共享指针传递给每个需要访问对象的线程(如果需要,包括构造对象的线程)
  • 当所有线程都释放了共享指针时,对象将被销毁

但显然,还存在其他有效的方法。关键是在物体生命周期的三个主要阶段之间保持适当的界限:建造、使用和破坏。绝不允许这些阶段之间有重叠。

它们不必被锁定在构造函数中,因为在这一点上,任何外部人员都可以访问该数据的唯一方法是从构造函数本身传递它们(或者执行一些未定义的行为,如调用虚拟方法)。

[编辑:删除了关于析构函数的部分,因为正如一条评论正确断言的那样,如果你试图从一个可能已经死了的对象访问资源,你会遇到更大的问题]