多线程操作C++ STL vector出现概率coredump问题分析------切勿对STL 容器的线程安全性有不切实际的依赖!

本文主要是介绍多线程操作C++ STL vector出现概率coredump问题分析------切勿对STL 容器的线程安全性有不切实际的依赖!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

       多线程操作全局变量,必须考虑同步问题,否则可能出现数据不一致, 甚至触发coredump.

       前段时间, 遇到一个多线程操作了全局的vector的问题,  程序崩了。场景是这样的:某全局配置参数保存在一个vector中,需要定时更新(更新线程), 另外的工作线程去读取配置。 这种场景是非常普遍的。

       在该场景中,程序没有枷锁,概率coredump, 实际情况是,服务跑了一段时间后,必然coredump.   很显然, 更新线程执行clear,然后在push_back操作时, 会导致工作线程的vector迭代器失效, 内存错误。

 

       本文中, 我从实例和代码的层面来说一下, 在C++ STL中, vector并不是线程安全的, 大家使用的时候, 要多加小心。 为了简便起见, 不采用上面的原场景, 而是仅仅以push_back为例:

       来看一段程序:

#include <pthread.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#define N 2
using namespace std;vector<int> g_v;
pthread_mutex_t mutex;void* fun(void *p)
{for(int i = 0; i < 100000; i++){//pthread_mutex_lock(&mutex);g_v.push_back(i);//pthread_mutex_unlock(&mutex);}return NULL;
}int main()
{pthread_t threads[ N];pthread_mutex_init(&mutex, NULL);for(int i = 0; i <  N; i++){pthread_create(&threads[i], NULL, fun, NULL);}for(int i = 0; i <  N; i++){pthread_join(threads[i],NULL);}cout << "ok" << endl;return 0;
}

        编译: g++ test.cpp  -lpthread -g

        运行3次:

taoge:~> ./a.out 
ok
taoge:~> ./a.out 
Segmentation fault (core dumped)
taoge:~> ./a.out 
ok

         可见, 程序概率core dump.  来调试一下:

taoge:~> gdb a.out core.9775 
GNU gdb 6.6
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i586-suse-linux"...
Using host libthread_db library "/lib/libthread_db.so.1".warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/libonion.so...done.
Loaded symbols for /lib/libonion.so
Reading symbols from /lib/libpthread.so.0...done.
Loaded symbols for /lib/libpthread.so.0
Reading symbols from /usr/lib/libstdc++.so.6...done.
Loaded symbols for /usr/lib/libstdc++.so.6
Reading symbols from /lib/libm.so.6...done.
Loaded symbols for /lib/libm.so.6
Reading symbols from /lib/libgcc_s.so.1...done.
Loaded symbols for /lib/libgcc_s.so.1
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/libdl.so.2...done.
Loaded symbols for /lib/libdl.so.2
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0  0x08048cc0 in __gnu_cxx::new_allocator<int>::construct (this=0x804a200, __p=0xb6cc2000, __val=@0xb7ce2464)at /usr/include/c++/4.1.2/ext/new_allocator.h:104
104           { ::new(__p) _Tp(__val); }
(gdb) bt
#0  0x08048cc0 in __gnu_cxx::new_allocator<int>::construct (this=0x804a200, __p=0xb6cc2000, __val=@0xb7ce2464)at /usr/include/c++/4.1.2/ext/new_allocator.h:104
#1  0x08049846 in std::vector<int, std::allocator<int> >::push_back (this=0x804a200, __x=@0xb7ce2464)at /usr/include/c++/4.1.2/bits/stl_vector.h:606
#2  0x08048bde in fun (p=0x0) at test.cpp:16
#3  0xb7f471eb in start_thread () from /lib/libpthread.so.0
#4  0xb7da97fe in clone () from /lib/libc.so.6
(gdb) f 2
#2  0x08048bde in fun (p=0x0) at test.cpp:16
16              g_v.push_back(i);
(gdb) i locals
i = 63854
(gdb) i args
p = (void *) 0x0
(gdb) f 1
#1  0x08049846 in std::vector<int, std::allocator<int> >::push_back (this=0x804a200, __x=@0xb7ce2464)at /usr/include/c++/4.1.2/bits/stl_vector.h:606
606                 this->_M_impl.construct(this->_M_impl._M_finish, __x);
(gdb) i locals
No locals.
(gdb) i args
this = (std::vector<int,std::allocator<int> > * const) 0x804a200
__x = (const int &) @0xb7ce2464: 63854
(gdb) p this
$1 = (std::vector<int,std::allocator<int> > * const) 0x804a200
(gdb) p *this
$2 = {<std::_Vector_base<int,std::allocator<int> >> = {_M_impl = {<std::allocator<int>> = {<__gnu_cxx::new_allocator<int>> = {<No data fields>}, <No data fields>}, _M_start = 0xb6c81008, _M_finish = 0xb6cc2000, _M_end_of_storage = 0xb6cc1008}}, <No data fields>}
(gdb) 

       重点关注frame 1, 其中有:_M_start, _M_finish, _M_end_of_storage, 熟悉vector底层动态分配的朋友, 应该能猜出这三个变量的含义, _M_start指向vector头, _M_finish指向vector尾, _M_end_of_storage指向预分配内存的尾。 来看下vector的push_back函数源码:

voidpush_back(const value_type& __x){if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage){_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);++this->_M_impl._M_finish;}else
#if __cplusplus >= 201103L_M_emplace_back_aux(__x);
#else_M_insert_aux(end(), __x);
#endif}

        可以看到, 在单线程环境下,  执行push_back的时候, _M_finish总是逐渐去追逐最后的_M_end_of_storage,,容量不够时继续扩_M_end_of_storage, 总之,_M_finish不会越过_M_end_of_storage.  但是, 在多线程环境下, 当_M_finish比_M_end_of_storage小1时,可能会出现多线程同时满足this->_M_impl._M_finish != this->_M_impl._M_end_of_storage, 然后同时执行++this->_M_impl._M_finish, 这样,_M_finish就越过了_M_end_of_storage, 如我们实验中的例子那样。越界操作导致有coredump。 当然, 具体是否越过, 是概率性的, 我们要避免这种未定义行为。

       怎么办呢?  可以考虑加锁, 把上述程序的注释取消, 也就是加了互斥锁(mutex), 实际多次运行发现, 再也没有coredump了。

 

        还有一个问题:  上面的结论是_M_finish越过了_M_end_of_storage, 导致coredump, 那如果让_M_end_of_storage不被越过呢? 理论上认为,不会core dump, 如下:

#include <pthread.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#define N 2
using namespace std;vector<int> g_v;
pthread_mutex_t mutex;void* fun(void *p)
{for(int i = 0; i < 100000; i++){//pthread_mutex_lock(&mutex);g_v.push_back(i);//pthread_mutex_unlock(&mutex);}return NULL;
}int main()
{g_v.reserve(999999);  // pay attentionpthread_t threads[ N];pthread_mutex_init(&mutex, NULL);for(int i = 0; i <  N; i++){pthread_create(&threads[i], NULL, fun, NULL);}for(int i = 0; i <  N; i++){pthread_join(threads[i],NULL);}cout << "ok" << endl;return 0;
}

       编译并运行多次, 未见coredump.  尽管如此, 也不能完全保证上述操作的结果符合预期的逻辑, 毕竟,多线程还在操作着非原子的push_back呢。

        

       最后,回到我遇到的那个问题,定时更新配置,可以考虑加锁。如果不用锁, 该怎么实现呢? 可以考虑用两个vector, 轮换使用,更新的vector不去读, 当前的读的vector不更新,然后轮换当前vector.  我见过很多地方都是这么用的。

 

       类似的问题还有很多很多, 坑, 就在那里, 不多不少。 书本Effective STL第12 条如是说:切勿对STL 容器的线程安全性有不切实际的依赖!

 

       不多说。

 

 

这篇关于多线程操作C++ STL vector出现概率coredump问题分析------切勿对STL 容器的线程安全性有不切实际的依赖!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/684093

相关文章

每天认识几个maven依赖(ActiveMQ+activemq-jaxb+activesoap+activespace+adarwin)

八、ActiveMQ 1、是什么? ActiveMQ 是一个开源的消息中间件(Message Broker),由 Apache 软件基金会开发和维护。它实现了 Java 消息服务(Java Message Service, JMS)规范,并支持多种消息传递协议,包括 AMQP、MQTT 和 OpenWire 等。 2、有什么用? 可靠性:ActiveMQ 提供了消息持久性和事务支持,确保消

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名