多线程里的 shared_ptr、引用和捕获线程安全注意点
多线程里的 shared_ptr、引用和捕获线程安全注意点引用计数本身是原子的但指针变量、引用别名、lambda 捕获都可能单独踩坑。本文说明什么是安全的、什么必须加锁、捕获时该拷贝还是引用。1.std::shared_ptrT一个std::shared_ptrT里实际有两块东西指向 T 的指针以及指向控制块的指针。控制块里的是引用计数。线程安全要分三层看控制块强引用计数、弱引用计数的增减是原子的。shared_ptr 这个变量本身那两个裸指针不是原子的。两个线程同时对同一个shared_ptr 对象做赋值、reset、swap会存在数据竞争导致未定义行为。T 对象本身shared_ptr 完全不管。两个线程同时改*p线程不安全。std::shared_ptrFoogstd::make_sharedFoo();// 安全每个线程拿自己的拷贝只动计数不动同一份 shared_ptr 变量voidthread_a(){autopg;// 拷贝use_count 1原子p-read();}// 不安全两个线程同时写 g 这个变量voidthread_b(){g.reset();// 和别人同时写 g数据竞争}// 不安全对象没保护voidthread_c(){g-value1;// 和别人同时改 Foo数据竞争}第一段auto p g读的是 g 里那两个指针然后给控制块加一。如果此时另有线程正在g.reset()改这两个指针那么这个拷贝不是线程安全的。因此常见的做法是用互斥锁或借助 C20 的std::atomicstd::shared_ptrT来保护这个共享变量。2. 同一个 shared_ptr 变量不能当共享内存下面这种写法很常见也很容易错std::shared_ptrFoocurrent;voidreplace(){currentstd::make_sharedFoo();}voiduse(){if(current){current-run();}}如果replace和use跑在不同线程上这里至少有两处问题读写current这个变量本身没有同步属于数据竞争if (current)和current-run()之间另一个线程可能已经 reset 了。判断时非空用的时候已经空或者指向一块正在析构的对象正确解决方式加锁把“读指针 用对象”放在同一把锁里。先在锁内拷贝出一份局部 shared_ptr锁外再用局部拷贝。生命周期被局部拷贝托住但修改对象时仍存在线程不安全问题。C20 起用std::atomicstd::shared_ptrFoo对指针变量做原子 load / store。这只保护指针变量不保护 Foo 内部。第二种是实际代码里最常用的方案std::mutex mu;std::shared_ptrFoocurrent;voidreplace(){autonextstd::make_sharedFoo();std::lock_guardstd::mutexlock(mu);currentstd::move(next);}voiduse(){std::shared_ptrFoolocal;{std::lock_guardstd::mutexlock(mu);localcurrent;// 锁内拷贝计数 1}if(local){local-run();// 锁外用Foo 不会因为别人 reset 而当场销毁}}注意local-run()期间别的线程仍可能通过自己的 shared_ptr 在改同一个 Foo。指针活着不等于对象内部线程安全。C20 的写法对应的是保护指针变量不是保护对象std::atomicstd::shared_ptrFoocurrent;voidreplace(){current.store(std::make_sharedFoo());}voiduse(){autolocalcurrent.load();if(local){local-run();}}3. 引用不延长生命周期引用、裸指针、解引用后的T都不影响引用计数。线程或回调只要比那个 shared_ptr 活得长引用就悬空。voidstart_work(std::shared_ptrFoop){Fooref*p;std::threadt([ref]{ref.run();// p 可能已经销毁ref 悬空});t.detach();}// 函数返回p 析构若这是最后一份Foo 没了这里有两层错误叠在一起Foo ref *p只是别名不增加计数lambda 用[ref]捕获的是引用的引用还是不增加计数线程里要用这个对象一定要把 shared_ptr按值传递voidstart_work(std::shared_ptrFoop){std::threadt([p]{// 按值捕获计数 1p-run();});t.detach();}函数参数std::shared_ptrFoo p已经是一份拷贝。再按值捕获一次线程持有的是又一份拷贝。函数返回后参数析构线程那份还在对象就还在。异步边界上捕获 shared_ptr 的引用、捕获*p得到的T都会有问题。4. lambda 捕获按值、按引用、this异步回调和std::thread的生命周期往往长过当前函数。捕获方式直接决定对象能不能活到回调执行。4.1 按值捕获 shared_ptr见34.2 按引用捕获autopstd::make_sharedFoo();std::threadt([p]{p-run();});t.detach();[p]只记住 p 这个变量的地址。p 是局部变量函数返回后这块栈就没了。这里崩的是 p 这个指针变量不是 Foo。4.3 捕获 this成员函数里开线程最容易写成structFoo{voidstart(){std::thread([this]{run();// 用的是裸 this}).detach();}voidrun(){/* ... */}};[this]捕获的是裸指针。Foo 被销毁后线程还在调run()就是悬空 this。如果 Foo 本身已经由 shared_ptr 管理用enable_shared_from_this在异步边界上按值捕获shared_from_this()structFoo:std::enable_shared_from_thisFoo{voidstart(){autoselfshared_from_this();std::thread([self]{self-run();}).detach();}voidrun(){/* ... */}};autofstd::make_sharedFoo();f-start();self是又一份 shared_ptr线程结束前 Foo 不会析构。C14 起可以直接写std::thread([selfshared_from_this()]{self-run();}).detach();Bar 按值捕获看起来像带走了所有权其实拷贝的是一个引用成员还是指向原来的 Foo。p 没了Bar 里的w一样悬空。成员里要跨线程持有对象存 shared_ptr不要存 T。5. weak_ptr观察不持有跨线程“看一眼对象还在不在”用 weak_ptr不要用裸指针也不要在没锁的情况下读别人的 shared_ptr 变量。std::weak_ptrFooobservercurrent;voidlater(){autopobserver.lock();// 原子地尝试把弱引用升级成强引用if(!p){return;// 已经销毁}p-run();}lock()要么得到一份有效的 shared_ptr要么得到空。不会出现“判断时还在、用的时候没了”这种把检查和使用拆开的窗口因为成功之后你已经持有强引用。缓存、观察、反向指针使用上安全。6. 对象内部仍然要自己同步即使每个线程都有一份 shared_ptr 拷贝它们指向的仍是同一个 T。下面两段可以同时发生// 线程 Ap-items.push_back(x);// 线程 Bp-items.push_back(y);这和用不用 shared_ptr 无关是std::vector的数据竞争。shared_ptr 只解决“谁来 delete”不解决“怎么改内部状态”。常见做法T 内部自己加 mutex所有可变接口都走这把锁外部规定所有对 T 的访问都在同一线程7. 注意点异步或进线程之前要延长对象寿命按值拷贝 shared_ptr或按值捕获shared_from_this()不要捕获 T、不要捕获 shared_ptr、不要捕获 this。多个线程要换掉“当前对象”这一个指针变量给这个变量加锁或用atomicshared_ptr。不要无同步地同时读写。锁内拷贝、锁外使用可以缩短持锁时间对象内部的并发修改仍要另做保护。观察用 weak_ptr::lock()不要用 get() 把裸指针寄到别的线程。想清楚最后一份 shared_ptr 会在哪个线程析构析构有没有线程亲和要求。[]和[]在异步里都过于笼统。需要什么就写什么[p]、[self shared_from_this()]。use_count()、if (p)之后再在没有持有局部拷贝的情况下跨线程用 p中间都可能被别人 reset。shared_ptr 不是对象的互斥锁。数据竞争出在 T 里时去加 T 的同步而不是再包一层智能指针。

相关新闻

嵌入式控制器如何解锁能量采集方案?从选型到调试的实战指南

嵌入式控制器如何解锁能量采集方案?从选型到调试的实战指南

做嵌入式这行这么多年,换电池是我最不想干的一件事。尤其是野外或者工业现场部署的传感器节点,为了换一颗电池,往往要爬塔、开柜、停机、办作业票,算下来人力成本比设备本身贵得多。后来我接触了能量采集(Energy Harve…

2026/10/21 5:20:15 阅读更多 →
rk3588 适配 Pcie2.0 硬盘

rk3588 适配 Pcie2.0 硬盘

rk3588 适配 Pcie2.0 硬盘 RK3588 的 PCIe 2.0 接口由 3 个独立的 Combo PHY(组合物理层) 提供。每个 PHY 均为 1 Lane(单通道) 配置,支持 PCIe 2.0 协议,理论单通道带宽为 500 MB/s。RK3588 的 PCIe 2.0 接口是其丰富高速扩展能力的重要组成部分,主要用于连接中等速度…

2026/10/23 13:49:54 阅读更多 →
SAPGUI程序架构解析:从BDC录屏代码理解屏幕、ABAP与数据字典的协同

SAPGUI程序架构解析:从BDC录屏代码理解屏幕、ABAP与数据字典的协同

1. 从一段“黑盒”代码说起:BDC录屏的初次邂逅 如果你刚开始接触SAP ABAP开发,第一次看到BDC(Batch Data Communication,批数据通信)录屏自动生成的代码,大概率会和我当初一样,感到一头雾水。屏…

2026/10/25 3:48:05 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/25 11:19:54 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/25 22:36:10 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/26 4:52:12 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/25 10:02:42 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/26 5:00:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/26 3:16:46 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/25 10:02:43 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/25 13:21:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 8:34:37 阅读更多 →