C++对象多线程生命周期管理

本文参考自陈硕《LinuxC++多线程服务端编程 使用muduo C++网络库》

C++中实现线程安全的一个类是很困难的,在某种意义上甚至是不可能的。

[JCP]中线程安全的定义

  • 多个线程同时访问,表现出正确的行为
  • 无论操作系统如何调度线程,无论线程执行顺序如何交织,表现出正确的行为
  • 调用端代码无需任何额外的同步或协调操作

根据这个定义,C++ STL中的类基本都是线程不安全的。

对象的线程安全大致可以分为三部分:

  1. 安全的创建
  2. 安全的调用
  3. 安全的销毁。

安全的创建

对象构造要做到线程安全很简单,唯一要求是构造时不要泄露this指针,即:

  • 不要在构造中注册任何回调。
  • 不要在构造中把this传给其他线程的对象。
  • 即便在构造函数的最后一行也不行。

构造函数执行期间对象没有完成初始化,this指针被泄露给其他对象可能会导致问题。

第三条要求意思是该类可能是一个基类,基类执行完成后可能还需要执行子类的构造,对象仍在构造中。

安全的调用

这个比较简单,使用mutex加锁即可。注意避免死锁。

死锁的常见情况

  1. 线程1和2均需要获取AB两个锁,但是线程1获取了A,线程2获取了B,于是死锁。

    例如:一个类内部使用mutex保护数据来实现线程安全调用。同时读写这个类的两个对象就有可能死锁,比如线程1 a=b,线程2 b=a,这样就可能造成死锁。

    解决方法:尝试加锁,失败后全部释放然后再次尝试。或者加锁时按照某个固定顺序加锁,比如先加锁地址较小的那个。

  2. 锁重入,一个锁被lock两次。

    例如:类A内部使用mutex保护数据,A::fun1()和A::fun2()都使用了mutex,如果func1中调用了func2,就会发生锁重入。

    一种更加隐蔽的重入:A支持指定一个回调函数,然后A::fun1内部会调用这个回调。然而指定的回调中调用了A::func2(),重入便发生了。

安全的销毁

这是C++中最困难的一点。如果一个对象能够被多个线程同时访问,那么对象的销毁时机将会变得模糊不清,出现多种竞态条件:

  1. 析构一个对象时,如何得知其他线程是否正在访问这个对象?
  2. 调用一个对象时,如何判断这个对象是否存活?
  3. 析构函数执行到一半,其他进程调用了这个对象,会发生什么情况?

类内部mutex无法解决问题1和问题2。在单线程中,可以通过在析构对象后设置指针为nullpr来标记这个对象已经被销毁。多线程中需要增加一个类外的mutex来处理。但是这就比较麻烦了。

问题3体现出了一个常常被忽略的隐含条件:使用类内部mutex进行数据保护时,这个mutex必须时有效的。一般情况下这个条件是满足的,但是当涉及析构函数时就会发生一个问题,析构函数可能会把这个mutex给销毁,mutex被销毁后其他线程准备获取该锁时会发生什么情况就只有天知道了。

类内部的mutex只能保证安全的调用,不能保证安全的析构,特别的,当调用到基类的析构时,子类的析构已经执行完成,基类拥有的mutex自然无法保护

必须要在所有线程都不访问这个对象时,才能安全的销毁一个对象。但是在C++中实现这一点很困难,我们必须手动销毁对象,却没法通过指针判断这个对象是否有人需要使用。自动垃圾回收在多线程编程中会起到重要作用,可以极大的减轻程序员的心智负担,比如java就可以很容易的处理1和2。所有人都用不到的东西一定是垃圾,这正式自动垃圾回收的原理。

使用智能指针来处理对象销毁问题

C++11中引入的智能指针可以很好的处理这个问题,主要的两个类是:shared_ptr和weak_ptr.

判断一个指针是否合法没有任何高效的方法,这是一切C++指针问题的根源。

智能指针解决了这个问题。智能指针作为一个中间层,将调用方和被调用方隔离开来,shared_ptr采用了引用计数的方式判断指针是否合法,每当增加一个调用方,计数加一,当调用方使用完毕后(比如调用方死亡)引用技术减一,当引用计数为0,就意味者已经没有人需要这个对象,对象就可以被销毁了。

智能指针的线程安全

智能指针是值语义,一般用作栈上对象,正常情况不会有shared_ptr* sp = new shared_ptr()这样的用法。所以它不会有销毁时的前两个问题

智能指针的引用计数是线程安全的,在主流平台上都是原子操作,没有用锁,性能优异。

如果要从多个线程读写同一个shared_ptr对象(析构也算写操作),那么需要加锁。

智能指针不负责保护其管理的对象的线程安全,被管理的对象的线程安全性不会因为通过智能指针调用而发生变化。

智能指针也会带来一些其他问题,详见智能指针一节。

posted @ 2020-08-18 10:00  loufand  阅读(1116)  评论(0编辑  收藏  举报