Реализация атомарной очереди в QT5

Я создаю довольно сложное приложение в Qt, которое может динамически загружать динамические библиотеки и запускать их как потоки, и они должны передавать информацию между собой как можно быстрее, поэтому я решил, что атомная очередь будет моим лучшим сценарием, так что это файл AtomicQueue.hpp:

#ifndef ATOMICQUEUE_HPP
#define ATOMICQUEUE_HPP

#include <QAtomicPointer>// Used http://www.drdobbs.com/parallel/writing-lock-free-code-a-corrected-queue/210604448?pgno=2
// as reference
template<class T>
class AtomicQueue
{
struct QueueNode
{
QueueNode( const T& value ) : next( NULL ), data( value ) {}
~QueueNode() { if ( next ) delete next; }
QueueNode   *next;
T           data;
};

public:
AtomicQueue()
{
m_front = new QueueNode( T() );
m_tail.store( m_front );
m_divider.store( m_front );
}

~AtomicQueue() {}

void push( const T& value )
{
m_tail.load()->next = new QueueNode( value );
m_tail = m_tail.load()->next; // This moves the QueueNode into the atomic pointer, making it safe :)
while( m_front != m_divider.load() )
{
QueueNode *tmp = m_front;
m_front = m_front->next;
delete tmp;
}
}

bool peek( T& result )
{
if ( m_divider.load() != m_tail.load() )
{
// Problem area
QueueNode *next = m_divider.load()->next;
if ( next )
{
result = next->data;
return true;
}
}
return false;
}

bool pop( T& result )
{
bool res = this->peek( result );
if ( res )
{
m_divider = m_divider.load()->next;
}
return res;
}

private:
QueueNode                   *m_front;
QAtomicPointer<QueueNode>   m_divider, m_tail;
};

#endif // ATOMICQUEUE_HPP

Очередь разрывается после того, как я нажимаю и выталкиваю один элемент, и я не могу понять, почему. Я довольно новичок в поточно-ориентированном программировании в стиле Atomic, поэтому вполне возможно, что я просто не понимаю специфический аспект этого. При работе в режиме отладки я получаю ошибку SEGSEV в моем bool AtomicQueue::peek функционировать, когда я делаю result = next->data,

Кто-нибудь может указать, что я делаю не так?

Так что я исправил проблему, которая была в моем деструкторе QueueNode. По сути, когда я удалял узел, он пытался очистить все дальнейшие узлы, что затем дало мне несколько разных путей для его исправления:

  1. Установите флаг в QueueNode, который говорит ему не удалять все ->next узлы
  2. Отсоедините передний узел перед удалением
  3. Разрешить AtomicQueue управлять очисткой узлов

Я выбрал третий вариант, так как он уже выполнил некоторую очистку при добавлении новых узлов, поэтому вот фиксированный класс для всех, кто заинтересован:

#ifndef ATOMICQUEUE_HPP
#define ATOMICQUEUE_HPP

#include <QAtomicPointer>// Used http://www.drdobbs.com/parallel/writing-lock-free-code-a-corrected-queue/210604448?pgno=2
// as reference
template<class T>
class AtomicQueue
{
struct QueueNode
{
QueueNode( const T& value ) : next( NULL ), data( value ) {}
~QueueNode() { /*if ( next ) delete next;*/ }
QueueNode   *next;
T           data;
};

public:
AtomicQueue()
{
m_front = new QueueNode( T() );
m_tail.store( m_front );
m_divider.store( m_front );
}

~AtomicQueue()
{
QueueNode *node = m_front;
while( node->next )
{
QueueNode *n = node->next;
delete node;
node = n;
}
}

void push( const T& value )
{
m_tail.load()->next = new QueueNode( value );
m_tail = m_tail.load()->next; // This moves the QueueNode into the atomic pointer, making it safe :)
while( m_front != m_divider.load() )
{
QueueNode *tmp = m_front;
m_front = m_front->next;
delete tmp;
}
}

bool peek( T& result )
{
if ( m_divider.load() != m_tail.load() )
{
// Problem area
QueueNode *next = m_divider.load()->next;
if ( next )
{
result = next->data;
return true;
}
}
return false;
}

bool pop( T& result )
{
bool res = this->peek( result );
if ( res )
{
m_divider = m_divider.load()->next;
}
return res;
}

private:
QueueNode                   *m_front;
QAtomicPointer<QueueNode>   m_divider, m_tail;
};

#endif // ATOMICQUEUE_HPP

7

Решение

Замечания: Это просто скопировано из моего вопроса, когда я выяснил проблему в своем коде. Спасибо @ peter-k за то, что указал на это, официально не получено ответа.

Так что я исправил проблему, которая была в моем деструкторе QueueNode. По сути, когда я удалял узел, он пытался очистить все дальнейшие узлы, что затем дало мне несколько разных путей для его исправления:

  1. Установите флаг в QueueNode, который говорит ему не удалять все ->next узлы
  2. Отсоедините передний узел перед удалением
  3. Разрешить AtomicQueue управлять очисткой узлов

Я выбрал третий вариант, так как он уже выполнил некоторую очистку при добавлении новых узлов, поэтому вот фиксированный класс для всех, кто заинтересован:

#ifndef ATOMICQUEUE_HPP
#define ATOMICQUEUE_HPP

#include <QAtomicPointer>// Used http://www.drdobbs.com/parallel/writing-lock-free-code-a-corrected-queue/210604448?pgno=2
// as reference
template<class T>
class AtomicQueue
{
struct QueueNode
{
QueueNode( const T& value ) : next( NULL ), data( value ) {}
~QueueNode() { /*if ( next ) delete next;*/ }
QueueNode   *next;
T           data;
};

public:
AtomicQueue()
{
m_front = new QueueNode( T() );
m_tail.store( m_front );
m_divider.store( m_front );
}

~AtomicQueue()
{
QueueNode *node = m_front;
while( node->next )
{
QueueNode *n = node->next;
delete node;
node = n;
}
}

void push( const T& value )
{
m_tail.load()->next = new QueueNode( value );
m_tail = m_tail.load()->next; // This moves the QueueNode into the atomic pointer, making it safe :)
while( m_front != m_divider.load() )
{
QueueNode *tmp = m_front;
m_front = m_front->next;
delete tmp;
}
}

bool peek( T& result )
{
if ( m_divider.load() != m_tail.load() )
{
// Problem area
QueueNode *next = m_divider.load()->next;
if ( next )
{
result = next->data;
return true;
}
}
return false;
}

bool pop( T& result )
{
bool res = this->peek( result );
if ( res )
{
m_divider = m_divider.load()->next;
}
return res;
}

private:
QueueNode                   *m_front;
QAtomicPointer<QueueNode>   m_divider, m_tail;
};

#endif // ATOMICQUEUE_HPP
0

Другие решения

Других решений пока нет …