2012-07-25 72 views
6

我相信我已经很好地掌握了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)?

+3

当你说的“类”是在多个线程之间使用,我假设你的意思是TestClass类型的对象可能在多个线程中使用。在这种情况下,你仍然只创建一个对象,所以你不需要在构造函数中锁定。如果两个线程同时在构造函数中,则它们将创建2个独立的对象。锁定对象构造更合理,以确保(例如)在对象完成构建之前不使用mValueTwo。析构函数似乎应该锁定,以确保数据在被销毁时不被访问。 – Rollie 2012-07-25 15:09:45

+0

@Rollie是的,我确实认为这个对象是共享的。所以,如果我创建了: – Brett 2012-07-25 15:17:32

+1

@Rollie:在破坏的时候访问类是一个带有实例生命周期管理的错误 - 当发生这种情况时程序已经被破坏(如果它可以在破坏时被访问,它也可能在事后发生)。 – 2012-07-25 15:18:18

回答

12

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

销毁是一个稍微困难的情况。但是,对象的正确生命周期管理可以确保在有可能某些线程仍然可以使用它时,对象不会被销毁。

一个共享指针可以帮助实现这个例如。 :

  • 构建体在某些线程对象
  • 通共享指针到每个需要的对象访问的线程(包括构造它,如果需要的线程)
  • 对象将被破坏时的所有主题已经发布了共享指针

但显然,其他有效的方法存在。关键是要在物体一生的三个主要阶段之间保持适当的界限:建筑,使用和销毁。切勿在这些阶段之间进行重叠。

+1

使用shared_ptr通常是不够的。这是因为放弃shared_ptr的最后一个线程将运行析构函数,但该线程可能不是修改对象的最后一个线程。如果对象包含复杂数据成员(如矢量或映射),并且析构函数不获取并释放锁,则析构函数可能会看到过时的内存并导致崩溃。 – 2014-12-14 23:59:27

+0

@MichiHenning:线程只能在完成对象的所有操作之后释放它的共享指针,这是一个非常合理的(如果不是明显的)要求。所以,锁定是不必要的。陈旧的记忆的确在理论上是可能的,但要处理这个问题,你需要一个记忆障碍,而不是锁定。 (当然,一些流行的线程库包含一些锁定操作的屏障,所以也许这就是你的意思) – 2014-12-15 20:22:40

+0

这里是场景。在线程A中创建shared_ptr 并将shared_ptr(正确联锁)传递给线程B.线程A更新foo的状态并删除其指针。后来,线程B放弃其shared_ptr,这导致线程B调用foo的析构函数。如果线程B自从线程A上次更新foo以来没有越过内存屏障,则foo的析构函数将对陈旧的数据进行操作。如果foo包含可能导致崩溃的复杂数据成员(如地图)。你说得对,需要记忆障碍。恰巧互斥体也造成了障碍。 – 2014-12-16 00:25:44

1

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

[编辑:析构函数左右去除部,因为作为一个评论理所当然断言,你有更大的问题,如果你从一个对象,这可能是死者试图访问资源]

+1

如果另一个线程可能在对象被销毁时访问该对象,则会出现错误,并且锁定将无济于事(请考虑如果销毁线程首先获取互斥锁,会发生什么情况)。另外,我没有看到在'shared_ptr'中包含互斥体将会实现什么。 – interjay 2012-07-25 15:20:06

+0

你是对的,我同意你有一个问题,如果你有机会从一个即将死亡的对象访问数据。 shared_ptr提议是为了避免互斥体与对象一起被销毁的情况,但我想你必须在访问该对象中的任何内容之前锁定该互斥体,因此如果它位于对象内部,则不起作用。我会编辑我的答案。 – 2012-07-25 15:47:55

相关问题