当前位置: 首页 > news >正文

c++多线程下崩溃一例分析 ACTIONABLE_HEAP_CORRUPTION heap failure block not busy DOUBLE

之前的三个代码接口使用了同一把锁,共享资源的访问是有序执行的没有问题。最近改成各个接口使用单独的锁,结果漏掉了共享资源的保护,于是出现了崩溃。最近与这个崩溃做斗争并定位找到的原因,成功复现了。这里总结下,后续涉及多线程访问的务必考虑周全。

崩溃信息描述

windbg工具分析

srv*C:\symbols*http://msdl.microsoft.com/download/symbols
!analyze -v

 以下是使用windbg工具分析出来的崩溃堆栈信息:

 

堆栈信息表明,发生了堆损坏(heap corruption),可能和双重释放(double free)有关。 

可能的原因

  1. 双重释放(Double Free)

    • 在某些情况下,可能会对同一个内存块进行两次释放操作,这会导致堆损坏和崩溃。
  2. 内存越界访问

    • 在代码中,可能会对内存进行越界访问,例如写入超出分配内存范围的数据,这也会导致堆损坏。
  3. 线程安全问题

    • setCache方法中没有使用,这可能导致多个线程同时修改sendBuffer_,从而引发数据竞争和堆损坏。

成员变量 std::string 非线程安全 

在C++标准库中,std::string 本身并不是线程安全的。尽管 std::string 的某些操作可能是线程安全的(例如,读取操作),但对其进行写操作时,仍然需要使用互斥锁来保护,以避免数据竞争和不一致。

线程安全的操作

  1. 读取操作
    • 多个线程可以同时读取同一个 std::string 对象,因为读取操作不会修改对象的状态。

非线程安全的操作

  1. 写操作
    • 多个线程同时对同一个 std::string 对象进行写操作会导致数据竞争和不一致。

崩溃复现过程

https://www.onlinegdb.com/

多线程执行以下代码,为了复现崩溃,setCache中没有加锁。

/******************************************************************************Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.*******************************************************************************/
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include <string.h>class ProtocolSawtooth {
public:struct protocolSendDataType{uint8_t dt1;uint8_t dt2;uint8_t dt3;uint8_t cmd;uint8_t res;uint8_t res1;uint8_t res2;uint8_t res3;};void setCache(const std::string& data) {//std::lock_guard<std::mutex> lock(mutex_);char send[50] = {0};memcpy(send, &sendDataType_, sizeof(sendDataType_));sendBuffer_ = std::string(send, sizeof(sendDataType_));}const std::string& getCache() const {std::lock_guard<std::mutex> lock(mutex_);return sendBuffer_;}private:protocolSendDataType sendDataType_{};std::string sendBuffer_;  // 成员变量mutable std::mutex mutex_;  // 互斥锁
};void threadFunction(ProtocolSawtooth& obj, const std::string& data) {obj.setCache(data);
}int main()
{std::cout<<"Hello World\n";ProtocolSawtooth obj;// 多线程示例const int numThreads = 1000;std::vector<std::thread> threads;for (int i = 0; i < numThreads; ++i) {threads.emplace_back(threadFunction, std::ref(obj), "Data from Thread " + std::to_string(i));}// 等待所有线程完成for (auto& thread : threads) {thread.join();}// 获取缓存数据const std::string& cache = obj.getCache();std::cout << "Cached data: " << cache << std::endl;return 0;
}

总结 

如果一个函数在其文档中没有特别注明具备线程安全性,则应该认为它不具备。许多库大量使用了内部的静态数据,除非它是为多线程应用所设计,否则要牢记其内部数据可能没有利用互斥量进行适当的保护。类似,如果类的成员函数在其文档中没有特别注明对于多线程应用是安全的话,则认为它不安全。两个线程去操作相同的对象会引起问题,这是显而易见的,然而,即使两个线程去操作不同的物体依然会引起问题。出于多种原因,许多类使用了内部静态数据或者在多个看上去明显不同的对象间共享实现细则。

一般准则

以下给出几个一般准则:

操作系统提供的API具备线程安全性

POSIX线程标准要求C标准库中的大多数函数具备线程安全性,少数例外会在C标准中注明。

对于Windows提供的C标准库,如果所使用的版本没有问题,而且进行了正确的初始化,他们都是安全的。

C++标准库的线程安全性不是很明确,它在很大程度上依赖于使用的编译器。标准模板库线程安全性的SGI准则作为实际中的标准取得很大进展,但并不是统一的标准。所以在使用时,需充分考虑多线程的并发安全。

其他资源

std string与线程安全_这才是现代C++单例模式简单又安全的实现_51CTO博客_C++ 线程安全 单例模式

https://zhuanlan.zhihu.com/p/705622208 

std::string 线程安全_c++ std::string 线程安全吗-CSDN博客

C++学习网 – 世界上最好的中文C++学习网站

std::string简介 – C++学习网

C++标准库中std::string对象在内存中的分配 - 脉脉

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 如何优化Oracle数据库的性能?
  • 多目标应用:基于自组织分群的多目标粒子群优化算法(SS-MOPSO)的移动机器人路径规划研究(提供MATLAB代码)
  • 计算机基础知识总结(八股文--计算机网络、操作系统、数据库、c++、数据结构与算法)
  • python web 框架 Tornado
  • GitHub项目评论被用来传播Lumma Stealer恶意软件
  • 【Linux】命令简介------迅速掌握Linux命令
  • html+css+js网页设计 文化历史 中华历史10个页面
  • ParallelsDesktop19可在任何Mac上运行Windows软件
  • python可视化-直方图
  • 解决Vscode Copilot连不上网问题
  • DWG如何转换成PDF?总结了四种转换
  • RocketMQ 实战:在 macOS 上安装与前端访问全流程指南
  • vue后台项目打包成桌面应用程序(.exe)
  • Netty 知识目录
  • vue2.0中axios请求配置
  • JavaScript-如何实现克隆(clone)函数
  • gitlab-ci配置详解(一)
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • JS函数式编程 数组部分风格 ES6版
  • node和express搭建代理服务器(源码)
  • PHP 小技巧
  • Redis 懒删除(lazy free)简史
  • vue总结
  • 服务器之间,相同帐号,实现免密钥登录
  • 关于 Cirru Editor 存储格式
  • 基于axios的vue插件,让http请求更简单
  • 讲清楚之javascript作用域
  • 一份游戏开发学习路线
  • 【运维趟坑回忆录 开篇】初入初创, 一脸懵
  • Linux权限管理(week1_day5)--技术流ken
  • 从如何停掉 Promise 链说起
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • !!java web学习笔记(一到五)
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (160)时序收敛--->(10)时序收敛十
  • (pytorch进阶之路)扩散概率模型
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (层次遍历)104. 二叉树的最大深度
  • (非本人原创)我们工作到底是为了什么?​——HP大中华区总裁孙振耀退休感言(r4笔记第60天)...
  • (七)Appdesigner-初步入门及常用组件的使用方法说明
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (一)SvelteKit教程:hello world
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • (转)ORM
  • .bat批处理(六):替换字符串中匹配的子串
  • .NET COER+CONSUL微服务项目在CENTOS环境下的部署实践
  • .Net Remoting(分离服务程序实现) - Part.3
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .net专家(高海东的专栏)
  • @cacheable 是否缓存成功_让我们来学习学习SpringCache分布式缓存,为什么用?
  • @RequestBody与@RequestParam:Spring MVC中的参数接收差异解析
  • [ C++ ] 类和对象( 下 )