[C++] STL map 在多執行緒下新增/刪除,造成 crash 的問題
最近在查一個 C++ 程式莫名其妙會當掉的問題…
蠻難查的,因為通常是跑了幾個小時後才會出現,
但如果拿 core dump 出現當時的資料重新跑一次,就好好的什麼事都沒發生…
core dump 裡面也常常是看不出什麼東西,一堆問號,載入 symbol 也沒什麼資訊…
開始做 code review 之後,首先懷疑是一個 STL vector 被設定成 class 的 static 變數,
其實就跟全域變數差不多了,但又有多個 thread 可以去修改/讀取,感覺就很可疑…
std::vector<int> Scanner::m_vecIdx; void Scanner::Add(int i) { if (std::find(m_vecIdx.begin(), m_vecIdx.end(), i) == m_vecIdx.end()) { m_vecIdx.push_back(i); } } void Scanner::Clear() { m_vecIdx.clear(); }
姑且先不論是否是這邊造成 crash,但不同 thread 理論上要處理不同的資料,
用全域變數共用資料顯然有問題,一定得修改…
因為程式裡有其他部分有用 thread id 來區分不同 thread 的資料,
所以先照原程式的寫法,把 vector 換成 map<pthread_t, vector>,
也就是每個 thread 有自己的 vector,才不會互相影響~
std::map<pthread_t, std::vector<int> > Scanner::m_mapIdx;
這樣改完之後,當掉的次數變少了,不過還是會當?!
又看了幾次程式,開始在想 multi-thread 在修改/讀取 map 時,會不會有問題呢?
看了幾篇文章,結論是:
– 對 map 來說,資料的新增/刪除不影響現有的 iterator
– 如果有資料的新增/修改的話,不能同時有其他 thread 對同一個 container 的存取
以上面的程式來說,雖然不同的 thread 會去使用 map 的不同部分,
例如 thread 1 用 m_mapIdx[1] 的 vector,thread 2 用 m_mapIdx[2] 的 vector,
但對 m_mapIdx 做新增/刪除的動作並不是 thread-safe 的…
假設突然有新的 thread 3 和 4 出現來呼叫 Add 函式,
程式寫法會導致 m_mapIdx 必須要新增給 thread 3 和 4 的資料,
但這動作不是 atomic 的,有可能在新增給 thread 3 的動作做一半時,
被切到 thread 4 去新增給 thread 4 的資料,導致 map 爛掉…
目前的改法是在使用到這個 map 的地方,都先用一個 mutex 保護住,
避免有兩個以上的 thread 同時去修改或讀取…
CAutoMutex autoMutex(g_mutex); pthread_t tid = pthread_self(); std::vector<int>& vecIdx = m_mapIdx[tid]; vecIdx.push_back(i);
當然上面這並不是最好的做法,理論上同時多根 thread 的讀取應該沒有問題,
但這得建立在 map 不會突然有新的 thread 進來要新增的前提之下…
在程式原作者放假回來之前,就只好先這樣頂著用了~~
參考資料:
stackoverflow: Iterator invalidation rules
stackoverflow: Thread-safety of reading and writing from/to std::map in C++
stackoverflow: What operations are thread-safe on std::map?