vector在多线程下的问题,迭代器失效造成程序崩溃。

最近在做项目的过程中,遇到STL中vector的多线程访问问题。问题大概是这样的:有一个全局的vector,一个写进程对该vector进行插入操作(push_back()),同时有一个读进程在监视该vector的内容并对其进行显示(操作:size(), at(i)),没有进行任何的线程同步,程序的编译没有任何问题,却一直出现运行时错误,主要是数组越界。 当时的考虑时:虽然vector不支持多线程,但是我的两个线程,一个写,一个却是只读,按理说不应该有问题,大不了有脏数据,但是我不在乎。网上有很多人说是线程同步问题,但是个人感觉不是这个问题。后来看了一篇网文之后才晃然大悟,其实是vector的大小动态分配所造成的问题。简单的说就是读进程和写进程在vector的内存动态重分配时内存地址不同步了(不知道说的对不对)。懒的再描述自己的具体问题了,直接把那篇网文贴上来,以供以后查看。

原文地址:http://donghao.org/2008/10/vectorouaissiiaaieia.html

原文内容:

程序出现了coredump,用gdb发现了出错的地方。

vector seq_offset;

......

if(seq_id < seq_offset.size())

{

seq_offset[seq_id]; //此处coredump

}

没有超过vector的大小,访问是不应该有问题的,而且这一现象是偶然出现,所以怀疑是多线程的原因。果然在另一个线程里发现对seq_offset有 insert操作,insert本身不会影响访问,但是当vector大小不够的时候,insert会引发内存重新分配。STL代码(linux gcc-3.4.3 /usr/include/c++/3.4.3/bits)里,vector的insert会调用_M_fill_insert,而当空间不够时 _M_fill_insert会重新分配空间,并将相关数据结构指向新空间。文件 vector.tcc

324 std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish); //销毁vector里的元素,即调用各元素的析构函数

325 _M_deallocate(this->_M_impl._M_start, //收回存放各元素的空间,即free

326 this->_M_impl._M_end_of_storage - this->_M_impl._M_start);

327 this->_M_impl._M_start = __new_start.base(); //数据结构指向新的空间

328 this->_M_impl._M_finish = __new_finish.base();

329 this->_M_impl._M_end_of_storage = __new_start.base() + __len;

麻烦就在于当326行结束后,_M_start指向的是一块已经释放了的空间,这时候再有vector的访问操作(如seq_offset[seq_id])当然就会coredump。其实STL完全可以先把_M_start赋给一个临时变量,然后_M_start指向 新空间,最后再释放临时变量指向的空间,这样可以保证在多线程下顺利运行。不过STL文档说了——它不支持多线程——所以这样做也没错。

加锁也可以解决这个问题,不过那样太低效了,不予考虑。最后的解决方案是,用vector的reserve方法预先分配好内存,免得在使用中动态增长。

///////////////////////////////////////////////////////////////////////////////////////////////

在STL的容器中,vector可以说是最容易理解和使用的容器了,以前使用数组的时候,如果不确定有多少数据要存储,就会预先分配一个大的数组,如果实际没有用到那么多,又会浪费很多的内存资源,如果不分配大的数组又担心不够用,有了vector之后,这些问题再也不用担心了,vector会动态的增长空间,当vector空间不足时会自动申请一片更大的内存空间,以存储新的数据。

vector动态内存增长的过程如下:

1) 申请一块更大的内存空间以存储新数据。

2) 将数据从旧内存空间拷贝到新内存空间。

3) 析构旧内存空间中的对象。

4) 释放旧内存空间

对于程序员来说,这简直就是一个神器,但是在享受vector带来方便的同时,它也隐藏了

一些内存方面的问题,需要我们注意一下

1、预先分配两倍的内存空间

对于一个数组,只要通过元素的个数以及元素的类型就可以得到占用的内存空间,而对于

vector情况就不一样,为了提高效率,vector会预先分配一定的内存空间,减少申请内存以及数据移动的开销,因此vector实际占用的内存空间要比需要的多一些。如果对内存要求比较高的程序,使用vector一定要小心了,当vector空间不足时,会申请一块约是当前占用内存两倍的新空间以存储更多的数据,若可用内存为1G,而当前vector已占用的内存空间0.5G,当再插入一个元素,vector将会申请一块约1G的内存空间,瞬间内存就被用光了,导致程序崩溃。每次扩容50%,94/2+94=141。

2、迭代器失效

当vector空间不足时,会申请一块更大的内存空间,并将原指向内存空间的数据移动到新

的内存空间中,接着,旧的内存空间将会被回收。这里就可能会出现问题了,当vector指向新的内存空间之后,原来指向旧内存空间的迭代器都会失效了,若再使用这些失效迭代器,将会出现coredump。因为当需要插入数据到vector的时候,就需要注意当前的迭代器还是否有效了。

3、释放vector所占内存

vector已经申请的内存空间并不会缩减,即使调用clear()函数,也只是清空vector的所有元素,真正占用的内存空间也不会减少。有一种方法可以释放vector占用的内存空间,swap()

函数可以交换两个vector对象所指向的内存空间,

下面例子说明了swap()函数的使用方法,假设拥有一个vector对象vec:

vector tmp; //tmp为空

vec.swap(tmp); //vec和tmp交换指向的内存空间。

如果代码想要更加精简,则可以选择下面的写法:

vec.swap(vector());//vec与临时对象交换了指向的内存空间。

上面两种方式都使用一个没有数据的vector对象与当前vetor对象进行了交换,对于vec来讲,其内存空间缩减为0,同理,若想vec变为指定大小,则只需要与一个大小一样的vector对象进行swap即可。

4、预先申请内存空间

若一开始就大概确定需要的内存空间,则可以使用vector的reserve()函数预先申请足够的内存空间,这样做,既可以减少内存增长时的申请内存和数据移动的开销,避免了迭代器失效的问题,也防止了申请了超出预期的的内存空间,导致内存耗尽的问题;若一开始不确定需要的内存空间,则可以先预留足够大的空间,当所有数据都插入后,通过swap()函数去除多余的容量即可。

你可能感兴趣的:(C++)