奇怪的shared_ptr行为

时间:2017-05-24 14:04:21

标签: c++ c++11 shared-ptr atomic lock-free

我在c ++中发现了一个非常奇怪的std::shared_ptr行为。 以下示例与标准指针完美配合。

std::shared_ptr的使用会导致分段错误。 (见下面的回溯)

我知道,从多个线程访问std::shared_ptr并不安全,因此我正在使用原子操作。即使是经典锁定也不会解决问题。

我正在使用gcc version 6.3.0 20170406 (Ubuntu 6.3.0-12ubuntu2)-Wall -O2 -g-std=c++17

有人知道解决方案或代码崩溃的原因吗?

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

template <typename T>
class list {
private:
    struct Node {
        T value;
        std::shared_ptr<Node> next;

        Node() : next(nullptr) {}
    };

    std::shared_ptr<Node> head;
public:
    // create dummy-nodes
    explicit list() : head(std::make_shared<Node>()){
        head->next = std::make_shared<Node>();
    };

    void push_front(T val) {
        std::shared_ptr<Node> current;
        std::shared_ptr<Node> next;
        std::shared_ptr<Node> newNode = std::make_shared<Node>();
        newNode->value = val;

        do {
            current = std::atomic_load<Node>(&head);
            next = std::atomic_load<Node>(&current->next);
            newNode->next = next;
        } while (!std::atomic_compare_exchange_weak<Node>(&current->next, &next, newNode));
    }
};

int main(int argc, char* argv[]) {

    list<int> ll;

    std::vector<std::thread> threads;
    const int thread_count = 8;
    const int local_count = 200000;

    std::mutex m;

    for (int i = 0; i < thread_count; i++) {
        threads.push_back(std::thread([&ll, i, local_count, &m]() {
            for (int j = local_count * i; j < local_count * (i + 1); j++) {
                m.lock(); // optional for testing; doesnt solve the problem
                ll.push_front(j);
                m.unlock();
            }

        }));
    }

    for (auto& thrd : threads) {
        thrd.join();
    }

    return 0;
}

GDB-回溯:

0x0000555555555d4e in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release (this=0x7fffd48eb0e0) at /usr/include/c++/6/bits/shared_ptr_base.h:150
150         _M_dispose();
(gdb) bt
#0  0x0000555555555d4e in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release (this=0x7fffd48eb0e0) at /usr/include/c++/6/bits/shared_ptr_base.h:150
#1  std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:662
#2  std::__shared_ptr<list<int>::Node, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:928
#3  std::shared_ptr<list<int>::Node>::~shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr.h:93
#4  list<int>::Node::~Node (this=<optimized out>, __in_chrg=<optimized out>) at src/main.cpp:11
#5  __gnu_cxx::new_allocator<list<int>::Node>::destroy<list<int>::Node> (this=<optimized out>, __p=<optimized out>) at /usr/include/c++/6/ext/new_allocator.h:124
#6  std::allocator_traits<std::allocator<list<int>::Node> >::destroy<list<int>::Node> (__a=..., __p=<optimized out>) at /usr/include/c++/6/bits/alloc_traits.h:487
#7  std::_Sp_counted_ptr_inplace<list<int>::Node, std::allocator<list<int>::Node>, (__gnu_cxx::_Lock_policy)2>::_M_dispose (this=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:529
#8  0x0000555555555d51 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release (this=0x7fffd48eb110) at /usr/include/c++/6/bits/shared_ptr_base.h:150
#9  std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:662
#10 std::__shared_ptr<list<int>::Node, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:928
#11 std::shared_ptr<list<int>::Node>::~shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr.h:93
#12 list<int>::Node::~Node (this=<optimized out>, __in_chrg=<optimized out>) at src/main.cpp:11
#13 __gnu_cxx::new_allocator<list<int>::Node>::destroy<list<int>::Node> (this=<optimized out>, __p=<optimized out>) at /usr/include/c++/6/ext/new_allocator.h:124
#14 std::allocator_traits<std::allocator<list<int>::Node> >::destroy<list<int>::Node> (__a=..., __p=<optimized out>) at /usr/include/c++/6/bits/alloc_traits.h:487
#15 std::_Sp_counted_ptr_inplace<list<int>::Node, std::allocator<list<int>::Node>, (__gnu_cxx::_Lock_policy)2>::_M_dispose (this=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:529
#16 0x0000555555555d51 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release (this=0x7fffd48eb140) at /usr/include/c++/6/bits/shared_ptr_base.h:150
#17 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:662
#18 std::__shared_ptr<list<int>::Node, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:928
#19 std::shared_ptr<list<int>::Node>::~shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr.h:93
#20 list<int>::Node::~Node (this=<optimized out>, __in_chrg=<optimized out>) at src/main.cpp:11
#21 __gnu_cxx::new_allocator<list<int>::Node>::destroy<list<int>::Node> (this=<optimized out>, __p=<optimized out>) at /usr/include/c++/6/ext/new_allocator.h:124
#22 std::allocator_traits<std::allocator<list<int>::Node> >::destroy<list<int>::Node> (__a=..., __p=<optimized out>) at /usr/include/c++/6/bits/alloc_traits.h:487
#23 std::_Sp_counted_ptr_inplace<list<int>::Node, std::allocator<list<int>::Node>, (__gnu_cxx::_Lock_policy)2>::_M_dispose (this=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:529
#24 0x0000555555555d51 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release (this=0x7fffd48eb170) at /usr/include/c++/6/bits/shared_ptr_base.h:150
#25 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:662
#26 std::__shared_ptr<list<int>::Node, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:928
#27 std::shared_ptr<list<int>::Node>::~shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr.h:93
#28 list<int>::Node::~Node (this=<optimized out>, __in_chrg=<optimized out>) at src/main.cpp:11
#29 __gnu_cxx::new_allocator<list<int>::Node>::destroy<list<int>::Node> (this=<optimized out>, __p=<optimized out>) at /usr/include/c++/6/ext/new_allocator.h:124
#30 std::allocator_traits<std::allocator<list<int>::Node> >::destroy<list<int>::Node> (__a=..., __p=<optimized out>) at /usr/include/c++/6/bits/alloc_traits.h:487
#31 std::_Sp_counted_ptr_inplace<list<int>::Node, std::allocator<list<int>::Node>, (__gnu_cxx::_Lock_policy)2>::_M_dispose (this=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:529
#32 0x0000555555555d51 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release (this=0x7fffd48eb1a0) at /usr/include/c++/6/bits/shared_ptr_base.h:150
#33 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:662
#34 std::__shared_ptr<list<int>::Node, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:928
#35 std::shared_ptr<list<int>::Node>::~shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr.h:93
#36 list<int>::Node::~Node (this=<optimized out>, __in_chrg=<optimized out>) at src/main.cpp:11
#37 __gnu_cxx::new_allocator<list<int>::Node>::destroy<list<int>::Node> (this=<optimized out>, __p=<optimized out>) at /usr/include/c++/6/ext/new_allocator.h:124
#38 std::allocator_traits<std::allocator<list<int>::Node> >::destroy<list<int>::Node> (__a=..., __p=<optimized out>) at /usr/include/c++/6/bits/alloc_traits.h:487
#39 std::_Sp_counted_ptr_inplace<list<int>::Node, std::allocator<list<int>::Node>, (__gnu_cxx::_Lock_policy)2>::_M_dispose (this=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:529
#40 0x0000555555555d51 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release (this=0x7fffd48eb1d0) at /usr/include/c++/6/bits/shared_ptr_base.h:150
#41 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:662
#42 std::__shared_ptr<list<int>::Node, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:928
#43 std::shared_ptr<list<int>::Node>::~shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr.h:93
#44 list<int>::Node::~Node (this=<optimized out>, __in_chrg=<optimized out>) at src/main.cpp:11
#45 __gnu_cxx::new_allocator<list<int>::Node>::destroy<list<int>::Node> (this=<optimized out>, __p=<optimized out>) at /usr/include/c++/6/ext/new_allocator.h:124
#46 std::allocator_traits<std::allocator<list<int>::Node> >::destroy<list<int>::Node> (__a=..., __p=<optimized out>) at /usr/include/c++/6/bits/alloc_traits.h:487
#47 std::_Sp_counted_ptr_inplace<list<int>::Node, std::allocator<list<int>::Node>, (__gnu_cxx::_Lock_policy)2>::_M_dispose (this=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:529
#48 0x0000555555555d51 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release (this=0x7fffd48eb200) at /usr/include/c++/6/bits/shared_ptr_base.h:150
#49 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:662
#50 std::__shared_ptr<list<int>::Node, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:928
#51 std::shared_ptr<list<int>::Node>::~shared_ptr (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/6/bits/shared_ptr.h:93
#52 list<int>::Node::~Node (this=<optimized out>, __in_chrg=<optimized out>) at src/main.cpp:11
#53 __gnu_cxx::new_allocator<list<int>::Node>::destroy<list<int>::Node> (this=<optimized out>, __p=<optimized out>) at /usr/include/c++/6/ext/new_allocator.h:124
#54 std::allocator_traits<std::allocator<list<int>::Node> >::destroy<list<int>::Node> (__a=..., __p=<optimized out>) at /usr/include/c++/6/bits/alloc_traits.h:487
#55 std::_Sp_counted_ptr_inplace<list<int>::Node, std::allocator<list<int>::Node>, (__gnu_cxx::_Lock_policy)2>::_M_dispose (this=<optimized out>) at /usr/include/c++/6/bits/shared_ptr_base.h:529

1 个答案:

答案 0 :(得分:8)

这是智能指针和列表节点的经典问题:节点的析构函数是递归的,最终会耗尽堆栈。这就是堆栈跟踪显示的内容:堆栈溢出。

CppCon 2016: Herb Sutter “Leak-Freedom in C++... By Default.”中,他们正好讨论了这个问题。