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
class lock_free_stack
{
    private:
        struct node
        {
            unique_ptr data;
            shared_ptr next;
            node(const T &v) : data(new T) {}
        }
        shared_ptr head;
    public:
        void push(const T &v)
        {
            auto new_node = make_shared(v);
            new_node->next = atomic_load(&this->head);
            while(!atomic_compare_exchange_weak(&this->head,&new_node->next,new_node)); 
        }
        unique_ptr 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,一定要考虑析构时调用栈不要溢出。

你可能感兴趣的:(C++)