Я работал над безблокировочным буфером C ++ 11 fifo. И я почти получил это. Однако одна маленькая деталь взяла верх над мной. У буфера есть голова, на которую указывают:
std::shared_ptr<node<T>> m_head;
Типа:
struct node
{
node(const T data)
:
data(new T(data)),
next(nullptr)
{}
std::shared_ptr<T> data;
std::shared_ptr<node<T>> next;
};
И тогда есть продукты:
void produce(const T &&data)
{
//bool indicating whether a notification should be sent after adding
bool l_notifyUponAdding;
//the new node to be added at the end of the array
std::shared_ptr<node<T>> l_newNode(new node<T>(std::forward<const T&&>(data)));
//pointer to the last node
std::shared_ptr<node<T>> l_lastNode(std::atomic_load(&m_head));
//value to compare the next of the last node with
std::shared_ptr<node<T>> l_expectedNullPointer;
//notify if this isn't the only node
l_notifyUponAdding = !l_lastNode;
if (!l_lastNode)//if there are no nodes, add this as the only node
if (std::atomic_compare_exchange_strong(&m_head, &l_expectedNullPointer, l_newNode))
return;
do
{
l_expectedNullPointer.reset();
while (l_lastNode->next)
{
l_lastNode = std::atomic_load(&l_lastNode)->next;
}
} while (!std::atomic_compare_exchange_weak(&l_lastNode->next, &l_expectedNullPointer, l_newNode));
//adding failed since another thread already did this.
l_lastNode = l_expectedNullPointer;if (l_notifyUponAdding)
m_newDataWaiter.notify_one();
}
И потреблять:
std::shared_ptr<T> consume(bool blockingCall = false)
{
//Check if the head is null if it is:
if (!std::atomic_load(&m_head))
{
if (blockingCall)//And this is a blocking call,
{
do
{
m_newDataWaiter.wait(m_newDataWaiterLock, [this]{return std::atomic_load(&(this->m_head)) == nullptr; });//we block until
} while (!std::atomic_load(&m_head));// the load yields a head that is not null(to avoid unnecessary calls on spurious wake ups)
}
else//And this is not a blocking call we
{
return nullptr;
}
}
//If we've found a valid head we will now try to make the node pointed to by head the new head.
std::shared_ptr<node<T>> l_poppee = atomic_load(&m_head);
std::shared_ptr<node<T>> l_newHead = atomic_load(&m_head);
//note that l_poppee gets updated if the compare exchange fails
while (l_poppee && !std::atomic_compare_exchange_weak(&m_head, &l_poppee, l_poppee->next))
{
}
if (l_poppee)
return l_poppee->data;
else
return std::shared_ptr<T>();
}
Функции.
Кажется, все работает хорошо. Однако я считаю, что есть один недостаток. Если все узлы используются во время выполнения produce
, Данные будут добавлены к последнему элементу. Хотя элемент уже был удален.
Чтобы быть более точным, если эта строка была выполнена:
if (std::atomic_compare_exchange_strong(&m_head, &l_expectedNullPointer, l_newNode))
И загруженный узел не был нулевым. Следующий элемент последнего узла будет изменен. Независимо от того, удаляются ли узлы тем временем или нет. Узлы не будут удаляться физически до тех пор, пока выполняется функция выполнения из-за общих указателей.
Тем не менее, основной указатель будет установлен в NULL. И, следовательно, новый узел будет удален, как только будет завершена функция произведения.
Кто-нибудь случайно знает решение этой проблемы?
Этот случай всегда решается в списках без блокировок, сохраняя фиктивный узел в списке. Голова всегда указывает на фиктивный узел, который является первым узлом в
список.
Когда очередь становится пустой, голова и хвост указывают на фиктивный узел.
Вы можете посмотреть на http://www.research.ibm.com/people/m/michael/podc-1996.pdf для деталей, просто, чтобы я не искажал концепцию, как это легко выбрать из статьи.