解决:无锁定队列多生产者多消费者-内存损坏

时间:2018-12-15 05:58:55

标签: c++11 stack queue producer-consumer memory-corruption

LockFreeQueueMPMC 应该解决无锁的MPMC问题,但是在运行时会损坏内存。 LockFreeDispatchStackMPMC 确实解决了无锁的MPMC问题,并用作LockFreeCacheMPMC分配器的基础。这两个实现都通过了压力测试。

LockFreeQueueMPMC队列执行与Lock FreeDispatchStackMPMC发送相同的操作。这会将一个新节点添加到列表中。出队操作更为复杂。一次只能有一个指针为cmpexg,因此使用Tail指针无法解决。为了从列表中删除节点,需要遍历列表并删除最后一个节点。这会将出队时间从O(1)更改为O(N),但没有锁定。

LockFreeDispatchStackMPMC是无序的MPMC无锁解决方案。最早到达的消息将首先得到服务。这使用堆栈而不是队列,对于某些问题,这是不可接受的,因为必须对消息进行排序。如果您的邮件可以无序排列,则表示与Queue相比性能提高了40%以上。

template<class T>
struct Node
{
    std::atomic<int> Next;
    T *Data;
};
template<class T>
class LockFreeDispatchStackMPMC
{
public:
    LockFreeDispatchStackMPMC()
    {
        Head = NULL;
    }
    ~LockFreeDispatchStackMPMC(){
    }
    void Send(T *item)
    {
        Node<T> * new_node = Cache.Malloc();
        new_node->Data=item;
        bool done = false;
        while(!done)
        {
            auto head = Head.load();
            new_node->Next.store( head);
            if( Head.compare_exchange_weak(head,new_node))
            {
                done = true;
            }
        }
    }

    T *Recieve()
    {
        T *returnValue = NULL;
        bool done = false;
        while(!done)
        {
            auto head = Head.load();
            if(head == NULL)
            {
                done=true;
            }
            else
            {
                Node<T> * curr = head;
                Node<T> *next = curr->Next.load();
                if(Head.compare_exchange_weak(head,next))
                {
                    done = true;
                    returnValue = curr->Data;
                    curr->Next =NULL;
                    Cache.Free(curr);
                }

            }
        }
        return returnValue;
    }
public:
    std::atomic<Node<T> *> Head;

private:
    LockFreeMemCache<Node<T> > Cache;
};

这是基于使用两个列表的“对象的高速缓存”容器,可用作跨线程池来存储对象。这也允许从墓地进行读取,因为这些对象尚未被破坏,因此不应允许写入。这是队列算法中的问题。即使每个节点必须一次分配一个,这也是MPMC安全的。

#define GROW_BY_SIZE 4

template<class T>
class LockFreeCacheMPMC
{
public:
    LockFreeCacheMPMC()
    {
        Head=NULL;
        FreeStack=NULL;
        AddSomeCache();
    }
    ~LockFreeCacheMPMC()
    {
        Node<T> *node ,*prev;

        bool done = false;
        node = Head;
        prev = NULL;
        while(!done)
        {
            prev = node;
            if(node == NULL)
            {
                done = true;
            }
            else
            {
                node = node->Next.load();
                delete prev->Data;
                delete prev;
            }
        }
        done = false;
        node = FreeStack;
        prev = NULL;
        while(!done)
        {
            prev = node;
            if(node == NULL)
            {
                done = true;
            }
            else
            {
                node = node->Next.load();
                delete prev;
            }
        }
    }
    T *Malloc()
    {
        T *returnValue = NULL;
        returnValue=Pop();
        while(returnValue==NULL)
        {
            AddSomeCache();
            returnValue=Pop();
        }
        return returnValue;
    }
    void Free(T *ptr)
    {
        Push(ptr);
    }

private:
    void AddSomeCache()
    {

        for(int i=0; i < GROW_BY_SIZE; i++)
        {
            T *tmp = new T();
            Push(tmp);
        }
    }

private:
    void Push(T *item)
    {
        Node<T> * new_node = PopNode(true);
        new_node->Data=item;

        bool done = false;
        while(!done)
        {
            Node<T>* head = Head.load();
            new_node->Next.store(head);
            if(Head.compare_exchange_weak(head,new_node))
            {
                done = true;
            }
        }
    }
    T *Pop()
    {
        T *returnValue = NULL;
        bool done = false;
        while(!done)
        {
            Node<T> * curr= Head.load();
            if(curr == NULL)
            {
                done=true;
            }
            else
            {
                Node<T> *next = curr->Next.load();
                if(Head.compare_exchange_weak(curr,next))
                {
                    done = true;
                    returnValue = curr->Data;
                    PushNode(curr);
                }
            }
        }
        return returnValue;
    }

    void PushNode(Node<T> *item)
    {
        item->Next = NULL;
        item->Data = NULL;
        bool done = false;
        while(!done)
        {
            Node<T>* fs = FreeStack.load();
            item->Next.store(fs);
            if(FreeStack.compare_exchange_weak(fs,item))
            {
                done = true;
            }
        }
    }
    Node<T> *PopNode(bool Alloc)
    {
        Node<T> *returnValue = NULL;

        bool done = false;
        while(!done)
        {
            Node<T> *fs = FreeStack.load();
            if(fs == NULL)
            {
                done=true;
            }
            else
            {
                Node<T> *next = fs->Next.load();
                if(FreeStack.compare_exchange_weak(fs,next))
                {
                    done = true;
                    returnValue = fs;
                }

            }
        }
        if ((returnValue == NULL) &&Alloc )
        {
            returnValue =new Node<T>();
            returnValue->Data = NULL;
            returnValue->Next = NULL;
        }
        return returnValue;
    }
    std::atomic<Node<T> *> Head;
    std::atomic<Node<T> *>FreeStack;
};

这是问题类别。它将运行一段时间,但是会发生损坏。问题出在出队方法中。节点一次从列表中删除。每一步都可能从您的下方修剪节点。这导致节点被修剪掉并需要“删除”,但是仍然有活动线程从该节点读取。该算法应防止对死节点的任何写入,因为原子上下一个指针要么指向一个节点,要么指向空节点,但是使用缓存池存储节点可以安全地从墓地读取数据。

template<class T>
class LockFreeQueueMPMC
{
public:
    LockFreeQueueMPMC()
    {
        Head=NULL;
    }
    ~LockFreeQueueMPMC(){
    }
    void Enqueue(T *item)
    {
        Node<T> * new_node = Cache.Malloc();
        new_node->Data=item;

        bool done = false;
        while(!done)
        {
            auto head = Head.load();
            new_node->Next.store(head);
            if(Head.compare_exchange_weak(head,new_node))
            {
                done = true;
            }
        }
    }
    T *Dequeue()
    {
        T *returnValue=NULL;
        bool done = false;
        while(!done)
        {
            Node<T> *head = Head.load();
            if(head == NULL)
            {
                done = true;
            }
            else
            {
                Node<T> * prev, *curr;
                prev = NULL;
                curr = head;
                bool found = false;
                while(!found)
                {
                    if(curr == NULL)
                    {
                        break;
                    }
                    Node<T> * next = curr->Next.load();
                    if(next == NULL)
                    {
                        found=true;
                        break;
                    }
                    prev = curr;
                    curr = next;
                }
                if(found)
                {
                    if(prev == NULL)
                    {
                        if(Head.compare_exchange_weak(head,NULL))
                        {
                            done = true;
                        }
                    }
                    else
                    {
                        if(prev->Next.compare_exchange_weak(curr,NULL))
                        {
                            done = true;
                        }
                    }
                    if(done)
                    {
                        returnValue = curr->Data;
                        Cache.Free(curr);
                    }
                }
            }
        }
        return returnValue;
    }
private:
    std::atomic<Node<T> *> Head;
    LockFreeMemCache<Node<T> > Cache;
};

问题位于出队方法中,有一个步骤可以破坏,但很少。

1 个答案:

答案 0 :(得分:0)

我建议您使用工具来帮助您,因为这不是简单易懂的代码。我不知道您使用的编译器,因为我是Linux开发人员,所以我可以建议以gcc为例-有可以使用的Thread清理程序。速度非常快,有可能您能够复制并赶上比赛条件:

How to use thread-sanitizer of gcc v4.8.1?