多線程操作C++ STL vector出現概率coredump問題及盡量避免鎖的雙緩沖隊列


多線程操作全局變量,必須考慮同步問題,否則可能出現數據不一致, 甚至觸發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函數源碼:

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 <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 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 容器的線程安全性有不切實際的依賴!

雙緩沖隊列文章,注意雙緩沖隊列不是完全不加鎖 而是盡可能的減少加減鎖

https://www.cnblogs.com/Forever-Kenlen-Ja/p/7811943.html

http://www.cnblogs.com/cqgis/p/6403262.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM