java迭代器创建后mutx锁,多线程操作C++ STL vector出现概率coredump问题及尽量避免锁的双缓冲队列...

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

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

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

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

来看一段程序:

#include

#include

#include

#include

#define N 2

using namespace std;

vector 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::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::construct (this=0x804a200, __p=0xb6cc2000, __val=@0xb7ce2464)

at /usr/include/c++/4.1.2/ext/new_allocator.h:104

#1 0x08049846 in std::vector >::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 >::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 > * const) 0x804a200

__x = (const int &) @0xb7ce2464: 63854

(gdb) p this

$1 = (std::vector > * const) 0x804a200

(gdb) p *this

$2 = {<:_vector_base> >> = {

_M_impl = {<:allocator>> = {<:new_allocator>> = {}, }, _M_start = 0xb6c81008,

_M_finish = 0xb6cc2000, _M_end_of_storage = 0xb6cc1008}}, }

(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函数源码:

void

push_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

#include

#include

#include

#define N 2

using namespace std;

vector 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 attention

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;

}

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

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

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

双缓冲队列文章,注意双缓冲队列不是完全不加锁 而是尽可能的减少加减锁

C&plus;&plus; STL vector容器学习

STL(Standard Template Library)标准模板库是C++最重要的组成部分,它提供了一组表示容器.迭代器.函数对象和算法的模板.其中容器是存储类型相同的数据的结构(如vector, ...

STL vector

STL vector vector是线性容器,它的元素严格的按照线性序列排序,和动态数组很相似,和数组一样,它的元素存储在一块连续的存储空间中,这也意味着我们不仅可以使用迭代器(iterator)访问 ...

STL vector用法介绍

STL vector用法介绍 介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和f ...

STL vector&plus;sort排序和multiset&sol;multimap排序比较

由 www.169it.com 搜集整理 在C++的STL库中,要实现排序可以通过将所有元素保存到vector中,然后通过sort算法来排序,也可以通过multimap实现在插入元素的时候进行排序.在 ...

STL vector 用法介绍

介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和for_each()中的使用.通 ...

STL vector使用方法介绍

介绍 这篇文章的目的是为了介绍std::vector,怎样恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和for_each()中的使用.通 ...

stl——vector详解

stl——vector详解 stl——vector是应用最广泛的一种容器,类似于array,都将数据存储于连续空间中,支持随机访问.相对于array,vector对空间应用十分方便.高效,迭代器使ve ...

C&plus;&plus;STL vector详解&lpar;杂谈&rpar;

介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和for_each()中的使用.通 ...

随机推荐

Web&lpar;Jsp&plus; Servlet&rpar;开发中如何解决中文乱码问题

1.中文乱码的成因 编码的字符集和解码的字符集不一致. 2.web开发过程中可能出现的乱码的位置及解决方案 ①request乱码 在向服务器传递数据时,所传递的中文有可能出现乱码. post请求(协议 ...

java实现简单的验证码(待增强)

package com.xxx; import java.awt.Color;import java.awt.Font;import java.awt.Graphics2D;import java.a ...

C&num;创建datatable

Asp.net DataTable添加列和行的方法 方法一: DataTable tblDatas = new DataTable("Datas"); DataColumn dc ...

获取某地的经纬度 &amp&semi;&amp&semi; 通过经纬度获取相应的地理位置

最近要通过一个经纬度判断该经纬度是否位于某个地区内,所以通过网上查找资料,整合后出了下面的内容. 1.通过地址获取改地址的经纬度 /** * @param addr * 查询的地址 * @return ...

编写高质量代码改善java程序的151个建议——导航开篇

2014-05-16 09:08 by Jeff Li 前言 系列文章:[传送门] 下个星期度过这几天的奋战,会抓紧java的进阶学习.听过一句话,大哥说过,你一个月前的代码去看下,慘不忍睹是吧.确实 ...

使用Jax-rs 开发RESTfull API 入门

使用Jax-rs 开发RESTfull API 入门 本文使用 Jersey 2开发RESTfull API.Jersey 2 是 JAX-RS 接口的参考实现 使用到的工具 Eclipse Neon ...

PHP闭包Closure与array&lowbar;reduce结合的一个范例

最近在研究laravel5.5的源代码,发现了其中的一段代码觉得挺有意思! 文件:vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.p ...

&lbrack;Swift&rsqb;LeetCode662&period; 二叉树最大宽度 &vert; Maximum Width of Binary Tree

Given a binary tree, write a function to get the maximum width of the given tree. The width of a tre ...

php处理文件的思考&lpar;去除空行、每行多余字符&rpar;

1.去除空行 <?php $str = file_get_contents('a.txt'); $str = explode(PHP_EOL, $str); //分割为数组,每行为一个数组元素 ...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值