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