线程安全的对象生命周期管理
线程安全的 class 应当满足的条件
- 多个线程同时访问时,其表现出正确的行为
- 无论操作系统如何调度这些线程,无论这些线程的执行顺序如何交织
- 调用端代码无须额外的同步或其他协调动作
对象创建的线程安全
对象创建要做到线程安全,唯一的要求就是在构造期间不要泄露 this 指针:
- 不要在构造函数中注册任何回调
- 不要在构造函数中把 this 传递给跨线程的对象
因为构造函数函数在执行期间还没有完成对象的初始化,如果 this 被泄露给其它对象(其自身创建的子对象除外),那么别的线程有可能访问这个半成品对象。
例如下面的写法是强烈不推荐的:
正确的写法:
析构函数的多线程安全问题
当一个对象被多个线程可见时,对象的销毁时机可能造成竞态条件:
- 析构一个对象时,如何得知此刻是否有别的线程正在执行该对象的成员函数?
- 在调用一个对象的成员函数之前,如何确保这个对象还存在,它的析构函数是否会碰巧执行了一半?
- 如何保证一个对象的成员函数执行期间,该对象不会被其他线程析构?
mutex 不是解决办法
例如:
作为 class 数据成员的 mutex 只能用于同步本 class 的其他数据成员的读和写,不能保证安全的析构。因为 mutex 成员的生命周期最多和对象一样长,而析构动作可以说是发生在对象的身亡之时(之后)。
对于基类对象,调用到基类析构函数时,派生类对象已经析构了,那么基类对象的 mutex 就不能完整的保护整个析构过程。
析构过程本质上来说,也不应该被 mutex 保护,因为只有保证别的线程访问不到这个对象时,析构才是安全的。即要想安全地销毁对象,最好在别的线程都看不到的情况下,偷偷地做。
shared_ptr/weak_ptr 解决方案
shared_ptr 是基于引用计数的,引用计数是自动化资源管理的常用方法,当引用计数降为 0 时,对象就被销毁。weak_ptr 也是一个计数型智能指针,但是它不增加引用计数,属于弱引用。
-
shared_ptr 控制对象的生命周期,只要有一个指向对象的 shared_ptr 存在,该对象就不会析构,当指向对象的最后一个 shared_ptr 析构或者 reset 时候,对象保证会被销毁
-
weak_ptr 不控制对象的生命周期,但是它知道对象是否还存在,如果对象存在,它可以提升为有效的 shared_ptr,如果对象不存在,则提升失败,返回一个空的 shared_ptr,提升的行为是线程安全的
shared_ptr 本身的线程安全性
shared_ptr 的引用计数是安全且无锁的,但是它本身不是线程安全的,要在多个线程中同时访问同一个 shared_ptr,正确的用法是加 mutex 保护。
shared_ptr 技术与陷阱
意外延长对象的生命周期
只有指向对象的 shared_ptr 有一个存在,对象就不会释放,从而在一些情况下导致对象的生命周期意外延长。
传参
执行 shared_ptr 的拷贝时需要修改引用计数,这个开销要比拷贝原始指针高,多数情况下可以使用 const reference 的方式传递,一个线程只需要在最外层函数有一个实体对象,之后都可以使用 const reference 的方式传递这个对象。
析构动作在创建时被捕获
- 虚析构不再是必须的
shared_ptr<void>
可以持有任何对象,而且能够安全释放- shared_ptr 对象可以安全地跨越模块边界,比如从 dll 中返回,而不会造成模块 A 分配的内存在模块 B 中释放的情况
- 二进制兼容性,即便 shared_ptr 指向的对象大小改变了,那么旧的客户代码仍然可以使用新的库,而无须重新编译
- 析构动作可以定制
虚析构不是必须的
shared_ptr<void>
可以持有任何对象
析构所在的线程
对象的析构是同步的,当最后一个指向对象的 shared_ptr 离开其作用域的时候,对象就会在同一个线程析构,这个线程不一定是对象诞生的线程,如果对象的析构十分耗时,那么可能会拖慢关键线程的速度,可以使用一个单独的线程专门处理析构,通过一个 BlockingQueue<shared_ptr<void>>
把对象的析构转移到专门的线程。
定制析构函数
假设:
这种写法导致的结果就是 map 中存放的 shared_ptr 一直不会被释放,Stock 对象不会被销毁。
修改为:
这样 Stock 对象会被销毁,但是 map 的大小只增不减。
终极的解决办法是定制 shared_ptr 的删除函数:
但是这里将 StockFactory 的 this 指针传递给了 bind,如果 StockFactory 先于 Stock 对象析构,那么当 Stock 析构的时候将会发生错误。
enable_shared_from_this
继承自 enable_shared_from_this 的类,可以将 this 指针变成 shared_ptr:
为了使用 shared_from_this,StockFactory 不能是栈上对象,必须是堆上对象,并且由 shared_ptr 管理其生命周期:
另外需要注意的是,shared_from_this 不能在构造函数中调用,因为在构造过程中对象还没有交给 shared_ptr 接管。
另外,一点就是 StockFactory 的生命周期似乎被意外延长了。shared_ptr 绑定到函数对象之后,那么回调的时候 StockFactory 对象始终存在,是安全的。但同时也使得 StockFactory 的生命周期不会短于函数对象。
弱回调
如果希望实现对象活着就调用对象,否则忽略之,这种方式称为 "弱回调"。这个技术的关键就是使用 weak_ptr:
由此,无论 Stock 和 StockFactory 谁先挂掉,都不会影响程序的正常运行。
__EOF__

本文链接:https://www.cnblogs.com/xiaojianliu/p/16131421.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App