欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++11的shared_ptr有可能导致函数调用栈溢出

程序员文章站 2024-02-29 09:37:58
...

最开始关注这个问题是在测试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,一定要考虑析构时调用栈不要溢出。