使用shared_ptr时的SEGFAULT

时间:2018-02-10 21:37:07

标签: c++ multithreading segmentation-fault shared-ptr double-free

我尝试使用shared_ptr在C ++中实现基于列表的惰性列表集。我的理由是,unreachable nodes将被最后shared_ptr自动释放。根据我的理解,shared_ptr's reference count上的递增和递减操作是原子的。这意味着只有 last shared_ptr 引用该节点才能为该节点调用 delete / free 。我为多线程运行程序,但我的程序崩溃时出现错误double free called分段错误(SIGSEGV)。我不明白这是怎么回事。下面给出了我的实现代码,方法名称表示它们的预期操作。

#include<thread>
#include<iostream>
#include<mutex>
#include<climits>

using namespace std;

class Thread
{
   public:
      std::thread t;  
};
int n=50,ki=100,kd=100,kc=100;`/*no of threads, no of inserts,deletes & searches*/`


class Node
{
public:
      int key;
      shared_ptr<Node> next;
      bool marked;
      std::mutex nodeLock;

      Node() {
         key=0;
         next = nullptr;
         marked = false;
      }

      Node(int k) {
         key = k;
         next = nullptr;
         marked = false;
      }

      void lock() {
         nodeLock.lock();
      }

      void unlock() {
         nodeLock.unlock();
      }

      ~Node()
      {
      }
};

class List {
   shared_ptr<Node> head;
   shared_ptr<Node> tail;

public:

   bool validate(shared_ptr<Node> pred, shared_ptr<Node> curr) {
      return !(pred->marked) && !(curr->marked) && ((pred->next) == curr);
   }

   List() {
      head=make_shared<Node>(INT_MIN);
      tail=make_shared<Node>(INT_MAX);
      head->next=tail;
   }

   bool add(int key)
   {
      while(true)
      {
         /*shared_ptr<Node> pred = head;
         shared_ptr<Node> curr = pred->next;*/
        auto pred = head;
        auto curr = pred->next;

         while (key>(curr->key))
         {
            pred = curr;
            curr = curr->next;
         }

         pred->lock();
         curr->lock();

         if (validate(pred,curr))
         {
            if (curr->key == key)
            {
               curr->unlock();
               pred->unlock();
               return false;
            }
            else
            {
                shared_ptr<Node> newNode(new Node(key));
               //auto newNode = make_shared<Node>(key);
                //shared_ptr<Node> newNode = make_shared<Node>(key);
                newNode->next = curr;
                pred->next = newNode;
                curr->unlock();
                pred->unlock();
                return true;
            }
         }
         curr->unlock();
         pred->unlock();
      }
   }

   bool remove(int key)
   {
      while(true)
      {
         /*shared_ptr<Node> pred = head;
         shared_ptr<Node> curr = pred->next;*/

        auto pred = head;
        auto curr = pred->next;

         while (key>(curr->key))
         {
            pred = curr;
            curr = curr->next;
         }

         pred->lock();
         curr->lock();

         if (validate(pred,curr))
         {
            if (curr->key != key)
            {
               curr->unlock();
               pred->unlock();
               return false;
            }
            else
            {
               curr->marked = true;
               pred->next = curr->next;
               curr->unlock();
               pred->unlock();
               return true;
            }
         }
         curr->unlock();
         pred->unlock();
      }
   }

   bool contains(int key) {
      //shared_ptr<Node> curr = head->next;
    auto curr = head->next;

      while (key>(curr->key)) {
         curr = curr->next;
      }
      return curr->key == key && !curr->marked;
   }
}list;

void test(int curr)
{
   bool test;
    int time;

    int val, choice;
    int total,k=0;
    total=ki+kd+kc;

    int i=0,d=0,c=0;

    while(k<total)
    {
        choice = (rand()%3)+1;

        if(choice==1)
        {
            if(i<ki)
            {
                val = (rand()%99)+1;
                test = list.add(val);
                i++;
                k++;
            }
        }
        else if(choice==2)
        {
            if(d<kd)
            {
                val = (rand()%99)+1;
                test = list.remove(val);
                d++;
                k++;
            }
        }
        else if(choice==3)
        {
            if(c<kc)
            {
                val = (rand()%99)+1;
                test = list.contains(val);
                c++;
                k++;
            }
        }
    }
}

int main()
{
   int i;

   vector<Thread>thr(n);

   for(i=0;i<n;i++)
   {
      thr[i].t = thread(test,i+1);
   }
   for(i=0;i<n;i++)
   {
      thr[i].t.join();
   }
   return 0;
}

我无法弄清楚上述代码有什么问题。错误每次都不同,其中一些只是SEGFAULTS

pure virtual method called
terminate called without an active exception
Aborted (core dumped)

请您指出我在上述代码中做错了什么?以及如何解决这个错误? 编辑:添加了非常粗略的test function,随机调用了三个list methods。此外,全局声明线程数和每个操作的数量。粗体编程,但它重新创建 SEGFAULT

1 个答案:

答案 0 :(得分:5)

问题是您没有使用shared_ptr的原子存储和加载操作。

控制块中的引用计数(参与特定共享对象的所有权的每个shared_ptr都指向shared_ptr的)是原子的,但是,数据shared_ptr本身的成员不是。

因此,将多个线程分别拥有自己的shared_ptr到共享对象是安全的,但是只要至少有一个线程允许多个线程访问相同的shared_ptr就不能保存。正在使用非const成员函数,这是您在重新分配next指针时正在执行的操作。

说明问题

让我们看一下libstdc ++ shared_ptr实现的(简化和美化)复制构造函数:

shared_ptr(const shared_ptr& rhs)
 : m_ptr(rhs.m_ptr),
   m_refcount(rhs.m_refcount) 
{ }

此处m_ptr只是指向共享对象的原始指针,m_refcount是一个执行引用计数的类,还处理对象m_ptr指向的最终删除。 / p>

可能出现问题的一个例子:假设当前一个线程正试图弄清楚列表中是否包含特定键。它从auto curr = head->next中的复制初始化List::contains开始。在它设法初始化curr.m_ptr之后,OS调度程序决定该线程必须暂停并在另一个线程中进行调度。

其他线程正在移除head的后继者。由于head->next的引用计数仍为1(毕竟,head->next的引用计数尚未被线程1修改),当第二个线程完成时删除节点它正在删除。

然后一段时间后第一个线程继续。它完成了curr的初始化,但由于m_ptr已经在线程2开始删除之前已经初始化,它仍然指向现在已删除的节点。当尝试比较key > curr->key时,线程1将访问无效的内存。

使用std :: atomic_load和std :: atomic_store来防止问题

std::atomic_loadstd::atomic_store通过在调用指针传入的shared_ptr的copy-constructor / copy-assignment-operator之前锁定互斥锁来防止出现此问题。如果对多个线程共享的shared_ptr的所有读取和写入都通过std::atomic_load / std::atomic_store resp。在另一个线程开始读取或修改相同的m_ptr时,一个线程只会修改shared_ptr但不会引用引用计数。

使用必要的原子加载和存储,List成员函数应如下所示:

bool validate(Node const& pred, Node const& curr) {
   return !(pred.marked) && !(curr.marked) && 
          (std::atomic_load(&pred.next).get() == &curr);
}

bool add(int key) {
    while (true) {
        auto pred = std::atomic_load(&head);
        auto curr = std::atomic_load(&pred->next);

        while (key > (curr->key)) {
            pred = std::move(curr);
            curr = std::atomic_load(&pred->next);
        }

        std::scoped_lock lock{pred->nodeLock, curr->nodeLock};
        if (validate(*pred, *curr)) {
            if (curr->key == key) {
                return false;
            } else {
                auto new_node = std::make_shared<Node>(key);

                new_node->next = std::move(curr);
                std::atomic_store(&pred->next, std::move(new_node));
                return true;
            }
        }
    }
}

bool remove(int key) {
    while (true) {
        auto pred = std::atomic_load(&head);
        auto curr = std::atomic_load(&pred->next);

        while (key > (curr->key)) {
            pred = std::move(curr);
            curr = std::atomic_load(&pred->next);
        }

        std::scoped_lock lock{pred->nodeLock, curr->nodeLock};
        if (validate(*pred, *curr)) {
            if (curr->key != key) {
                return false;
            } else {
                curr->marked = true;
                std::atomic_store(&pred->next, std::atomic_load(&curr->next));
                return true;
            }
        }
    }
}

bool contains(int key) {
    auto curr = std::atomic_load(&head->next);

    while (key > (curr->key)) {
        curr = std::atomic_load(&curr->next);
    }
    return curr->key == key && !curr->marked;
}

此外,您还应将Node::marked设为std::atomic_bool