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,一定要考虑析构时调用栈不要溢出。