关于std::string 在 并发场景下 __grow_by_and_replace free was not allocated 的异常问题
使用string时发现了一些坑。
我们知道stl 容器并不是线程安全的,所以在使用它们的过程中往往需要一些同步机制来保证并发场景下的同步更新。
应该踩的坑还是一个不拉的踩了进去,所以还是记录一下吧。
string作为一个容器,随着我们的append 或者 针对string的+ 操作都会让string内部的数据域动态增加,而动态增加的过程则伴随着一些局部指针变量的创建和释放,而当我们并发对同一个string进行操作的时候(测试很明显,写工程项目因为专注于各个模块细节,这一些问题因为代码功底不够,还是没有办法注意到位),就会出现一些double-free 这样的异常问题(double-free 即 对同一个地址释放了两次,第一次对一个地址free的时候这段内存已经还给了操作系统,当第二次访问这个地址则就是非法访问了)。
看看下面这个测试代码,大体逻辑就是多线程从一个已有的string数组中并发将数组中的内容取出编码到一个全局的string里面。
#include <iostream>#include <string.h>
#include <thread>
#include <unistd.h>
#include <vector>#include <assert.h>using namespace std;std::vector<std::string> data_vec;
std::string dst;char* EncodeVarint32(char* dst, uint32_t v) {// Operate on characters as unsignedsuint8_t* ptr = reinterpret_cast<uint8_t*>(dst);static const int B = 128;if (v < (1 << 7)) {*(ptr++) = v;} else if (v < (1 << 14)) {*(ptr++) = v | B;*(ptr++) = v >> 7;} else if (v < (1 << 21)) {*(ptr++) = v | B;*(ptr++) = (v >> 7) | B;*(ptr++) = v >> 14;} else if (v < (1 << 28)) {*(ptr++) = v | B;*(ptr++) = (v >> 7) | B;*(ptr++) = (v >> 14) | B;*(ptr++) = v >> 21;} else {*(ptr++) = v | B;*(ptr++) = (v >> 7) | B;*(ptr++) = (v >> 14) | B;*(ptr++) = (v >> 21) | B;*(ptr++) = v >> 28;}return reinterpret_cast<char*>(ptr);
}inline void PutVarint32(std::string* dst, uint32_t v) {char buf[5];char* ptr = EncodeVarint32(buf, v);// If the v is a negative number, we need store the last byte.dst->append(buf, static_cast<size_t>(ptr - buf));
}inline void PutLengthPrefixedSlice(std::string* dst, const std::string& value) {PutVarint32(dst, static_cast<uint32_t>(value.size()));dst->append(value.data(), value.size());
}void EncodeTo(std::string* dst, std::string key) {PutLengthPrefixedSlice(dst, key);
}void EncodeDataVec() {std::cout << "Encode data_vec" << std::endl;for (int i = 0;i < data_vec.size(); i ++) {EncodeTo(&dst, data_vec[i]);}
}void ConstructDataVec() {std::cout << "Construct data_vec" << std::endl;for (int i = 0;i < 10; i++) {data_vec.emplace_back(std::to_string(i));}
}int main(int argc, char* argv[]) {int threads = 1;if (argc == 2) {threads = atoi(argv[1]);}std::cout << "threads : " << threads << std::endl;ConstructDataVec();for (int i = 0;i < threads; i++) {new std::thread(EncodeDataVec);}return 0;
}
当我设置并发数为20的时候,很明显出现如下问题
./concurrent_test 20## 异常问题,大体就是释放了一个不存在的地址
concurrent_test(5008,0x700008738000) malloc: *** error for object 0x7fefab504080: pointer being freed was not allocated
concurrent_test(5008,0x700008738000) malloc: *** set a breakpoint in malloc_error_break to debug
[1] 5008 abort ./concurrent_test 20
lldb一下看看:
lldb ./concurrent_test 20
(lldb) target create "./concurrent_test"
Current executable set to '/Users/zhanghuigui/Desktop/work/source/cpp_practice/data_structure/string/concurrent_test' (x86_64).
(lldb) settings set -- target.run-args "20"
(lldb) r
Process 5828 stopped
* thread #4, stop reason = signal SIGABRTframe #0: 0x00007fff2030c462 libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
-> 0x7fff2030c462 <+10>: jae 0x7fff2030c46c ; <+20>0x7fff2030c464 <+12>: movq %rax, %rdi0x7fff2030c467 <+15>: jmp 0x7fff203066a1 ; cerror_nocancel0x7fff2030c46c <+20>: retq
(lldb) bt
异常栈的信息如下
core在了grow_by_and_replace
中,这个函数被string::append
函数调用,也就是我们上面测试代码底层的Encode逻辑会调用这个append,然后grow_by_and_replace
就是为了对容器进行扩容。
基本实现代码如下:
我们可以发现在grow_by_and_replace 在实现扩容的逻辑过程中需要分配新的地址空间,将旧的数据拷贝到新的地址,这个过程需要借用临时指针,并且在完成拷贝之后释放老的地址old_p
。
很明显,我们并发append全局string的时候,这里的old_p
的释放并不是线程安全的,两个线程同时append,且都需要进行扩容,则一个扩容完成释放旧指针,但是旧指针还在被另一个线程引用,则第二个线程扩容完成释放旧指针,显然是访问了一个空的地址了。
除了并发问题之外,使用string 不断得append的时候 还会有性能问题,因为append扩容期间 会不断得有数据拷贝,而内存拷贝是很浪费时间的,所以string使用时如果能够预知容量,建议reserve
足够的空间,能够避免动态分配空间造成的性能问题,当然,如果提前reserve的话 也不会有 grow_by_and_replace
这个问题的。
在main
函数中,调用线程逻辑之前增加dst.reserve(10000)
,则并发100线程 跑100轮都不会有问题了。
但是在我们实际的应用过程中想要解决 这个string 并发扩容时造成的内存泄漏问题,我们还需要有其他的办法。
局部构造好之后赋值给一个全局变量std::string
即可:
void EncodeDataVec() {std::cout << "Encode data_vec" << std::endl;std::string tmp_dst;for (int i = 0;i < data_vec.size(); i ++) {EncodeTo(&tmp_dst, data_vec[i]);}dst = std::string(tmp_dst);
}
相关文章:

Java项目:银行管理系统+文档Java基础Gui(java+Gui)
源码获取:博客首页 "资源" 里下载! 功能介绍: 登录、打印、取款、改密、转账、查询、挂失、存款、退卡 服务模块: public class atmFrame extends JFrame {private JPanel contentPane;private user user; // private…

ie旋转滤镜Matrix
旋转一个元素算是一个比较常见的需求了吧,在支持CSS3的浏览器中可以使用transform很容易地实现,这里有介绍:http://www.css88.com/archives/2168,这里有演示http://www.css88.com/tool/css3Preview/Transform.html,就不…

音频(3):iPod Library Access Programming Guide:Introduction
NextIntroduction介绍iPod库访问(iPod Library Access)让应用程序可以播放用户的歌曲、有声书、和播客。这个API设计使得基本播放变得非常简单,同时也支持高级的搜索和播放控制功能。iPod library access 通过打开iOS允许的音乐相关的广阔范围…

【2019/4/30】周进度报告
冲刺可以推迟了,但这不妨碍知识储备(另外这周看了看梦断代码,感觉还是很有意思的一本书)。 第七周所花时间约9个小时代码量700多行,主要是阅读代码为主(框架内代码)博客量1篇了解到的知识点 1.y…

关于 智能指针 的线程安全问题
先说结论,智能指针都是非线程安全的。 多线程调度智能指针 这里案例使用的是shared_ptr,其他的unique_ptr或者weak_ptr的结果都是类似的,如下多线程调度代码: #include <memory> #include <thread> #include <v…

Java项目:无库版商品管理系统(java+Gui+文档)
源码获取:博客首页 "资源" 里下载! 功能介绍: 添加商品、修改商品、删除商品、进货出货、查看流水、注册 登录业务处理: public class LoginView extends JFrame implements ComponentListener{private JPanel center…

LTE QCI分类 QoS
http://blog.163.com/gzf_lte/blog/static/20840310620130140057204/ http://blog.163.com/gzf_lte/blog/static/208403106201301403652527/ http://blog.sina.com.cn/u/1731932381 lte2010 QCI (QoS Class Identifier)同时应用于GBR和Non-GBR承载。一个QCI是一个值࿰…

CSS 单行溢出文本只显示部分内容
.cc-item div { width:175px; text-overflow:clip; //该属性适用于IE6,IE7 max-width:175px; //该属性适用于IE8,FF,谷歌}

Audio声音
转载于:https://www.cnblogs.com/kubll/p/10799187.html

Rocksdb Ribbon Filter : 结合 XOR-filter 以及 高斯消元算法 实现的 高效filter
文章目录前言XOR-filter 实现原理xor filter 的构造原理xor filter 构造总结XOR-filter 和 ADD-filter对比XOR-filter 在计算上的优化Ribbon filter高斯消元法总结参考前言 还是起源于前几天的Rocksdb meetup,其中Peter C. Dillinger 这位大佬分享了自己为rocksdb实…

Java项目:无库版银行管理系统(java+Gui+文档)
源码获取:博客首页 "资源" 里下载! 功能介绍: 注册用户、编辑用户、删除用户、存取款、查看流水 存入业务处理: public class depositFrame extends JFrame {private JPanel contentPane;private JTextField inputFiel…

iptables-save和iptables-restore
iptables-save用来把当前的规则存入一个文件里以备iptables-restore使用。它的使用很简单,只有两个参数:iptables-save [-c] [-t table]参数-c的作用是保存包和字节计数器的值。这可以使我们在重启防火墙后不丢失对包和字节的统计。带-c参数的iptables-s…

代码之美——Doom3源代码赏析2
http://www.csdn.net/article/2013-01-17/2813778-the-beauty-of-doom3-source-code/2 摘要:Dyad作者、资深C工程师Shawn McGrathz在空闲时翻看了Doom3的源代码,发出了这样的惊叹:“这是我见过的最整洁、最优美的代码!”“Doom 3的…

什么是JavaBean
按着Sun公司的定义,JavaBean是一个可重复使用的软件组件。实际上JavaBean是一种Java类,通过封装属性和方法成为具有某种功能或者处理某个业务的对象,简称bean。由于javabean是基于java语言的,因此javabean不依赖平台,具…

关于 linux io_uring 性能测试 及其 实现原理的一些探索
文章目录先看看性能AIO 的基本实现io_ring 使用io_uring 基本接口liburing 的使用io_uring 非poll 模式下 的实现io_uring poll模式下的实现io_uring 在 rocksdb 中的应用总结参考先看看性能 io_uring 需要内核版本在5.1 及以上才支持,liburing的编译安装 很简单&am…

添加引用方式抛出和捕获干净的WebService异常
转载:http://www.cnblogs.com/ahdung/p/3953431.html 说明:【干净】指的是客户端在捕获WebService(下称WS)抛出的异常时,得到的ex.Message就是WS方法中抛出的异常消息,不含任何“杂质”。 前提:…

Java项目:车租赁管理系统(java+Gui+文档)
源码获取:博客首页 "资源" 里下载! 功能介绍: 登陆界面、管理员界面、用户界面、汽车租赁文档 系统主页: SuppressWarnings("serial") public class SystemMainView extends JFrame implements ActionListe…

TFS中的测试计划(十)
现在有一个测试用例,用来测试登录,并且有两组测试数据。打开团队项目的web门户的测试。新建一个测试计划。命名为测试计划1添加完测试计划后,就可以向这个计划里添加测试用例了,选择登录测试。运行测试,就会生成下图左…

跟着Rocskdb 学 存储引擎:读写链路的代码极致优化
文章目录1. 读链路1.1 FileIndexer1.1.1 LevelDB sst查找实现1.1.2 Rocksdb FileIndexer实现1.2 PinnableSlice 减少内存拷贝1.3 Cache1.3.1 LRU Cache1.3.2 Clock Cache1.4 ThreadLocalPtr 线程私有存储1.4.1 version系统1.4.2 C thread_local vs ThreadLocalPtr1.4.3 ThreadL…

Java项目:人力管理系统(java+Gui+文档)
源码获取:博客首页 "资源" 里下载! 功能介绍: 角色员工、管理员,员工信息表,查询、更新,修改,移除、添加 用户管理控制层: /*** author yy*/Controller RequestMapping(…

senfile函数实例的运行过程截图
//要传输的文件内容如下所示: 启动服务器,等待客户端连接(在同一台主机上模拟的) 客户端远程登录,这里是在本地登录 这个要注意一点就是远程登陆的时候一定要带上端口号不然连接失败!!转载于:ht…

马年计划2014-2-21
新的一年到来了! 刚刚过去的一年里,我已浪费很多时光! 新年新气象,为避免重蹈覆辙,此时我必须要立个新年计划,马年计划! (1)一天必须做两道ACM题。 (2&#…

java jsp页面如何添加C标签
在https://mvnrepository.com/找两个jar包分别是: <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> <version>1.2</version> </dependency> <dependency> <g…

如何用 ndctl/ipmctl 管理工具 配置不同访问模式的pmem设备
文章目录1 PMEM 底层架构2 PMEM 逻辑架构3 ipmctl 创建 不同模式的 region3.1 安装3.2 创建AppDirect mode的region3.3 创建 Memory Mode模式3.4 创建 混合模式3.5 查看创建的结果4 ndctl 创建不同类型的 namespaces4.1 安装4.2 创建/删除 一个任意类型的namespace4.3 指定类型…

[PHP]php基础练习题学习随笔
1、解释一下PHP中常量、变量、可变变量并举例说明;超级全局变量有哪些? 常量是单个值的标识符(名称),通过define()设置,在脚本中无法改变该值,常量自动全局。<?php #对大小写不敏感为true&a…

Java项目:进销存系统(java+Gui)
源码获取:博客首页 "资源" 里下载! 功能介绍: 基本信息管理、库存管理、销售管理、订单管理、日志管理、供应商基本信息、员工基本信息、商品信息、入库管理、出库管理、剩余库存 商品信息控制层: /*** <p>* 前…

IDP申请直到软件上架流程 - iOS
第一:IDP的申请 1.先在iPhone DevCenter上注册成为iphone developer 2.加入iPhone开发程序项目iPhone Developer Program Apply Now 3.打算收费的都建议选择99刀那个,QTY是个数的意思。1就好。 4.选择地区china,(很早之前没有china࿰…

灭霸—个人冲刺(4)
灵魂三问:昨天做了什么?1.手机验证码 2h 2.整体框架搭建尝试 2h 目标任务量:100% 完成任务量:100% 今天要做什么?1.数据库建立及连接 16h 遇到困难没有?2.整体框架搭建时因为连接服务器分为三类…

关于 Rocksdb 的 EnvWrapper 作用的小讨论
临下班前一位做引擎的小伙伴提了个小问题, Rocksdb 实现了非常多的Env backend 这一些backend 可以让用户根据自己需求创建不同 公共接口backend,来实现自己的文件操作或者公共线程池操作。 Env* env new rocksdb::HdfsEnv(FLAGS_hdfs) 问题是…

corepython第九章:文件和输入输出
学习笔记: OS模块代码示例: 1 import os2 for tmpdir in (/tmp,rc:\users\administrator\desktop):3 #如果存在括号里面的目录,则break4 if os.path.isdir(tmpdir):5 break6 #如果不存在,则tmpdir为空值,即False7 else:8 pri…