众所周知,std容器是非线程安全的,跟非线程安全的容器,如果代码core掉,通常会在容器的一些方法函数中。因为这类的core文件往往显示不是很直观,很多c++ std新手往往对这类型core无从下手。所以这里做一些纪录以总结通用经验给后人享用。
一个专业背景推荐知识是:这里设计了一个数据结构m_cvr2是进行cvr打分。
std::unordered_map<int32_t,std::unordered_map<int64_t,double> > m_cvr2;
分别对应着<场景id,<应用广告id,打分>>
。分为一些场景,然后每个场景都以单独线程发给上游预估服务打分。线程间是并行计算过程。最后汇聚所有场景的打分信息。
有一天,开发代码进行了灰度发布,隔一段时间会有个core文件。使用gdb打印了信息如下。
把m_cvr2的内容进行了打印(因为容器元素很多,这里使用了gdb内置命令set logging on,将std out屏幕输出写份副本到文件名gdb.txt。作为对比,我们也打印了m_ctr和m_cvr容器)
可以看到这个m_cvr2的[场景id为258]的map没有clear成功。
对比m_ctr或者m_cvr的[场景id为258]的map已经成功clear了。
这次出现的core文件出现在unordered_map::clear()。对m_cvr2[theme_id]的unordered_map操作出了异常。
也就是这里m_cvr2的数据结构是这样定义:
std::unordered_map<int32_t, std::unordered_map<int64_t, double>> m_cvr2;
然后对m_cvr2[theme_id]剥离得到了一个结构体为std::unordered_map<int64_t, double>的成员。这个成员本身也是一个unordered map,它也不是线程安全的。这里有个背景要说明的是,因为我们通过theme_id做了线程的区分。比如说有theme_id从1...30,那么有对应的30个m_cvr2[theme_id],所以可以保证每个线程只限制访问到自己的m_cvr2[theme_id]。
所以其实这个clear操作是没有问题的。
那么是什么导致的。首先我们需要了解到std容器operator[index]背后的机制。他首先会去根据index索引find是否存在这个元素,如果没有,会进行new一个新成员,并insert到容器中,之后就操作这个元素。如果有find这个元素,那么就直接操作这个元素了。
所以这里有多少个them,就有多少个线程并行竞争访问操作这个m_cvr2。如果是只读这个m_cvr2本身是没问题的,但是一旦有线程没有find去创建新元素,那就会导致其他对m_cvr2的操作有几率出现core文件。
所以避免问题的一个办法是,对于并发线程来说,我们只要保证他们是只读访问就行了。那么只读访问,我们可以在初始化统一有一个线程去把所有元素都创建出来。那么后续线程并发访问使用operator[index]操作都有现成的元素可以使用。这里类似m_ctr和m_cvr,把元素都初始化预填充出来。
这类问题起因不是直观的,因为不是我们stl中容器的clear实现有bug,本质上我们没有很熟悉容器是非线程安全的特性。所以总结一些使用std容器的一些准则,
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。