C++11的shared_ptr有可能導致函式呼叫棧溢位
阿新 • • 發佈:2018-12-11
最開始關注這個問題是在測試C++ Concurrency in Action這本書提及的幾個版本stack資料結構的實現,其中lock free版本的實現時,需要精巧的記憶體回收機制,其中在介紹count reference記憶體回收機制時,作者認為shared_ptr是有reference count的指標,如果某個平臺支援lock free版本的shared_ptr,可以使用它來簡化count reference記憶體回收方式的實現。
但是我想指出的是,這個實現方式,有可能導致呼叫棧溢位,我當時使用4個執行緒向stack中push資料,4個執行緒從stack中pop資料,每個執行緒操作100萬個資料,就會偶爾出現呼叫棧溢位的問題。
使用shared_ptr使用的lock free版本的stack程式碼大概如下
template<typename T> class lock_free_stack { private: struct node { unique_ptr<T> data; shared_ptr<node> next; node(const T &v) : data(new T) {} } shared_ptr<node> head; public: void push(const T &v) { auto new_node = make_shared<node>(v); new_node->next = atomic_load(&this->head); while(!atomic_compare_exchange_weak(&this->head,&new_node->next,new_node)); } unique_ptr<T> pop(void) { auto old_head = atomic_load(&this->head); while(old_head && !atomic_compare_exchange_weak(&this->head,&old_head,old_head->next)); return old_head ? move(old_head->data) : nullptr; } };
這段程式碼的問題在於pop操作的old_head,如果在其執行析構前,某個執行緒的CPU被剝奪,後續所有的node都不能析構,因為old_head->next這個shared_ptr還指向後續的node,依次類推,即使node已經從stack中刪除,但是也由於還有shared_ptr指向它,而不能被釋放,當CPU再次排程給該執行緒時,以old_head為頭的、通過next連線到一起的、已經從stack中刪除的node可能非常的多,導致我們遞迴呼叫node解構函式的層級太深,進而導致呼叫棧溢位!
這個例子涉及系統的執行緒排程,系統為執行緒分配的時間片的大小,還有就是原子操作的使用,所以算是比較隱晦,如果針對性的測試,很容易就能測試出這個問題。
所以使用shared_ptr來連結node,一定要考慮析構時呼叫棧不要溢位。