1. 程式人生 > >C++11的shared_ptr有可能導致函式呼叫棧溢位

C++11的shared_ptr有可能導致函式呼叫棧溢位

最開始關注這個問題是在測試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,一定要考慮析構時呼叫棧不要溢位。