Is this usage of condition variable wrong? - c++

In the chapter 6 of C++ Concurrency in Action, it implements a thread-safe queue. Here is the complete code. But I find there may be something wrong with its use of condition variable.
std::unique_lock<std::mutex> wait_for_data()
{
std::unique_lock<std::mutex> head_lock(head_mutex);
data_cond.wait(head_lock, [&] {return head.get() != queue::get_tail(); });
return std::move(head_lock);
}
void push(T new_value)
{
std::shared_ptr<T> new_data(
std::make_shared<T>(std::move(new_value)));
std::unique_ptr<node> p(new node);
{
std::lock_guard<std::mutex> tail_lock(tail_mutex);
tail->data = new_data;
node *const new_tail = p.get();
tail->next = std::move(p);
tail = new_tail;
}
data_cond.notify_one();
}
The consuming part locks head_mutex, but the producing part locks tail_mutex, possibly causing the consumer to miss notifications. Am I rihgt?

Related

Why thread keeps hanging during condition_variable.wait()

I'm trying to write a program, that uses threads. The thread should sleep and wait until head pointer of Stack changes, then do some stuff and sleep again. However, my thread keeps hanging on wait function, and my program hangs with it, waiting for statement to change. But it won't, because whole program waiting to thread complete. So.. Here is my code, it's working, when I put the thread join into destructor, but I want it to run before the pushes happening, so it can notice changes during run, and then when int main() completed, safely terminated.
#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
#include <condition_variable>
struct Node {
int data;
Node* next;
};
class Stack {
private:
Node* head;
std::mutex mtx;
std::thread writeThread;
std::ofstream outFile;
Node* prevHead;
std::condition_variable cv;
public:
Stack() {
head = nullptr;
prevHead = nullptr;
outFile.open("C:\\Users\\mayday\\Desktop\\stack.txt");
writeThread = std::thread(&Stack::WriteStack, this);
}
~Stack() {
writeThread.join();
}
void Push(int data) {
std::unique_lock<std::mutex> lock(mtx);
Node* newNode = new Node();
newNode->data = data;
newNode->next = head;
head = newNode;
lock.unlock();
cv.notify_one();
}
void WriteStack() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return head != prevHead; });
Node* temp = head;
while (temp != nullptr) {
outFile << temp->data << " ";
temp = temp->next;
}
outFile << std::endl;
outFile.close();
prevHead = head;
lock.unlock();
}
}
};
int main() {
Stack s;
s.Push(1);
s.Push(2);
s.Push(3);
return 0;
}
I tried creating new void method for thread join() to execute it from main, but if I implement it before Push(), then it will sleep forever and do nothing. Even with calls from destructor, it's just doing stuff and keeps freezing waiting on condition changes. Of course I can provide it a way out by checking head == prevHead in the end, and then breaking it, but I want it just to sleep in background, until it will be notified for changes. How can I overcome this freeze?
Sorry for my bad english.
A few things:
As I called out the comments, there's no reason to explicitly invoke lock.unlock() since the destructor of unique_lock will do that for you.
Also, I tend to favor notify_all() instead of notify_one() just so I don't have to think about what additional edge cases that might create.
But neither of the above issues are the reason for your hang.
Your code is hanging because there's nothing to signal to the thread to exit. Your main thread just hangs on a call to writeThread.join() in the Stack destructor waiting for the worker thread to exit. And your worker thread is just hanging out waiting to be notified again.
Add a new bool member to your class called needToExit initialized to false in the constructor.
In the destructor, set this variable to true (while under a mutex lock) and then signal the cv.
In your thread, check this variable as an exit condition.
Update. Based on your comments, I also added a change to Push such that it will wait for the worker thread to finish processing a previous head pointer change. A cv.notify_all call is made from the thread when to signal back to the main thread each time it finishes processing a head change.
class Stack {
private:
Node* head;
std::mutex mtx;
std::thread writeThread;
std::ofstream outFile;
Node* prevHead;
std::condition_variable cv;
bool needToExit; // DECLARE THIS
public:
Stack() {
head = nullptr;
prevHead = nullptr;
needToExit = false; // INIT TO FALSE
outFile.open("C:\\Users\\jselbie\\Desktop\\stack.txt");
writeThread = std::thread(&Stack::WriteStack, this);
}
~Stack() {
{
std::unique_lock<std::mutex> lock(mtx);
needToExit = true; // SET UNDER LOCK IN DESTRUCTOR
}
cv.notify_all(); // signal cv to wake up thread to check condition
writeThread.join();
}
void Push(int data) {
std::cout << "push(" << data << ")\n";
// wait for any previous head change to
// be picked up and processed by the thread
cv.wait(lock, [this]() {
return (head == prevHead)
});
std::unique_lock<std::mutex> lock(mtx);
Node* newNode = new Node();
newNode->data = data;
newNode->next = head;
head = newNode;
cv.notify_all();
}
void WriteStack() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() {
// CHECK needToExit in addition to pointer change
return ((head != prevHead) || needToExit);
});
if (head != prevHead) {
Node* temp = head;
while (temp != nullptr) {
outFile << temp->data << " ";
temp = temp->next;
}
outFile << std::endl;
outFile.close();
prevHead = head;
cv.notify_all(); // notify main thread
}
if (needToExit)
break; // EXIT THREAD WHEN needtoExit==true
}
}
}
};

C++ Queue Implementation using the Default Move Assignment Operator

My group and I attempted to implement a queue in C++ based on the following example for the code of a stack.
template<typename T>
class Stack {
private:
struct Node {
T data;
Node* next;
Node(T data, Node* next): data(data), next(next) {}
// No destructor here; it is a private inner class and would be slow
// to delete a chain of nodes this way!
};
private:
Node* head;
public:
Stack(): head(nullptr) {}
~Stack() {
while (head != nullptr) {
Node* old_head = head;
head = head->next; // update
delete old_head;
}
}
// Prohibit copy construction and copy assignment
Stack(Stack&) = delete;
Stack& operator=(Stack&) = delete;
// Wait wait I want to be able to move
Stack(Stack&&) = default;
Stack& operator=(Stack&&) = default;
public:
int getSize() {
int count = 0;
for (Node* n = head; n != nullptr; n = n->next) count++;
return count;
}
bool isEmpty() {
return head == nullptr;
}
void push(T x) {
head = new Node(x, head);
}
T pop() {
if (isEmpty()) {
throw underflow_error("Cannot pop from empty stack");
}
Node* nodeToDelete = head;
T poppedValue = head->data;
head = head->next;
delete nodeToDelete;
return poppedValue;
}
T peek() {
if (isEmpty()) {
throw underflow_error("Cannot peek into empty stack");
}
return head->data;
}
};
Just like the stack example, we attempted to implement move for the queue using the same ideas, including the default move assignment operator. Doing this, we came across problems such as invalid operands errors, collapsed stack frames and more when running our code.
template < typename T >
class Queue {
private:
struct Node {
T data;
Node * next;
Node(T data, Node * next): data(data), next(next) {}
};
private: Node * head;
private: Node * tail;
public:
Queue(): head(nullptr), tail(nullptr) {}~Queue() {
while (head != nullptr) {
Node * old = head;
head = head -> next;
delete old;
}
}
Queue(Queue & ) = delete;
Queue & operator = (Queue & ) = delete;
Queue(Queue && ) = default;
Queue & operator = (Queue && ) = default;
public:
int get_size() {
int count = 0;
for (Node * n = head; n != nullptr; n = n -> next)
count++;
return count;
}
bool isEmpty() {
return head == nullptr;
}
void enqueue(T x) {
Node * temp = new Node {x, nullptr};
if (head == nullptr) {
head = temp;
tail = temp;
} else {
tail -> next = temp;
tail = temp;
}
}
T dequeue() {
if (isEmpty()) {
throw underflow_error("Cannot dequeue from empty queue");
}
Node * nodeToDelete = head;
T poppedValue = head -> data;
head = head -> next;
delete nodeToDelete;
return poppedValue;
}
};
We ended up making our own move assignment operator which the queue worked as expected. The following code is what we ended up with.
Template <typename T>
class Queue {
private:
struct Node {
T data;
Node *next;
Node(T data, Node *next) : data(data), next(next) {}
};
private:
Node *head;
private:
Node *tail;
public:
Queue() : head(nullptr), tail(nullptr) {}
~Queue() {
while (head != nullptr) {
Node *old = head;
head = head->next;
delete old;
}
}
Queue(Queue &) = delete;
Queue &operator=(Queue &) = delete;
Queue(Queue &&) = default;
Queue &operator=(Queue &&other) {
if (&other == this) {
return *this;
}
delete head;
head = other.head;
tail = other.tail;
other.head = nullptr;
other.tail = nullptr;
return *this;
}
public:
int get_size() {
int count = 0;
for (Node *n = head; n != nullptr; n = n->next) {
count++;
}
return count;
}
bool is_empty() {
return head == nullptr;
}
void enqueue(T x) {
Node *temp = new Node{x, nullptr};
if (head == nullptr) {
head = temp;
tail = temp;
} else {
tail->next = temp;
tail = temp;
}
}
T dequeue() {
if (is_empty()) {
throw underflow_error("Cannot dequeue from empty queue");
}
Node *nodeToDelete = head;
T poppedValue = head->data;
head = head->next;
delete nodeToDelete;
return poppedValue;
}
Although we ultimately went with the latter implementation, we were really curious as to why the default move assignment operator worked for the stack and not the queue. We were wondering if it was because we created our own destructor for the class and disabled the copy operator, but we are unsure. Perhaps we were going about the implementation wrong? Any help/ feedback would be much appreciated!
Generally, you should always consider implement your own move assignment operator and move constructor when it comes to dynamic memory allocation.
Your stack one just appeared to work, but actually it is not correct.
Try to add these to your stack, and you will see the problem:
Stack <int> func() {
Stack <int> s;
s.push(3);
return s;
}
int main()
{
Stack <int> b;
b = func(); // assignment move
return 0;
}
func() is returning a Stack with non-null head member variable (this is critical)
When leaving func(), local variable s is destructed
When leaving main(), local variable b is destructed (problem here)
That's why this code causes a double-free problem in your Stack.
(BTW, your Queue assignment move operator may cause memory leak, since delete head; may not be what you want to do.)

stack overflow during recursion in C++

I wrote a template class for Singly linked list. For printing values in reverse order, I implemented traverse_reverse() function using recursion. When the number of elements in list reaches near 4000, calling this function threw stack overflow error.
At such range of numbers, I am not sure if stack overflow should happen.
Environment is Visual Studio 2019 Community edition, Windows 10 64 bit OS.
My questions are:
How can I avoid stack overflow
How can I increase the size of stack at runtime.
Below is the code snippet:
#pragma once
#include <mutex>
#include <iostream>
namespace MyDS
{
template <typename T>
struct Node
{
T* m_pData = nullptr;
Node* m_pNext = nullptr;
};
template <class T>
class sList
{
Node<T>* m_pHead = nullptr;
Node<T>* m_pCurrentNode = nullptr;
int m_Size = 0;
std::mutex m_ListMutex;
public:
bool insert_front(T val);
bool insert_last(T val);
bool insert_anywhere(T val, int loc);
bool remove(T val);
//bool remove(int loc);
bool remove_front();
bool remove_last();
void traverse();
void traverse_reverse();
bool emptyList();
int getSize();
private:
void traverse_reverse(Node<T>* pNode);
};
template<typename T>
void sList<T>::traverse_reverse(Node<T>* pNode)
{
if (pNode->m_pNext != nullptr)
traverse_reverse(pNode->m_pNext);
std::cout << *pNode->m_pData << " ";
}
template<typename T>
bool sList<T>::emptyList()
{
bool ret = false;
if (getSize() > 0)
{
std::lock_guard<std::mutex> lg(m_ListMutex);
Node<T>* pTempNode = m_pHead, pTempNode1 = nullptr;
while (pTempNode->m_pNext!= nullptr)
{
pTempNode1 = pTempNode->m_pNext;
delete pTempNode->m_pData;
delete pTempNode;
pTempNode = pTempNode1;
}
delete pTempNode->m_pData;
delete pTempNode;
pTempNode->m_pData = pTempNode1->m_pData = m_pHead->m_pData = m_pCurrentNode->m_pData = nullptr;
pTempNode = pTempNode1 = m_pHead = m_pCurrentNode = nullptr;
m_Size = 0;
}
ret = true;
return ret;
}
template<typename T>
int sList<T>::getSize()
{
return m_Size;
}
template<typename T>
bool sList<T>::insert_front(T val)
{
Node<T>* pNode = new Node<T>;
pNode->m_pData = new T(val);
if (getSize() > 0)
{
pNode->m_pNext = m_pHead;
}
m_pHead = pNode;
m_Size++;
return true;
}
template<typename T>
bool sList<T>::insert_last(T val)
{
Node<T>* plastNode = m_pHead;
while (plastNode->m_pNext!= nullptr)
plastNode = plastNode->m_pNext;
plastNode->m_pNext = new Node<T>;
plastNode->m_pNext->m_pData = new T(val);
return true;
}
template<typename T>
bool sList<T>::insert_anywhere(T val, int loc)
{
return true;
}
//template<typename T>
//bool sList<T>::remove(int loc)
//{
// return true;
//}
template<typename T>
bool sList<T>::remove_front()
{
std::lock_guard<std::mutex> lg(m_ListMutex);
Node<T>* pNode = m_pHead;
m_pHead = m_pHead->m_pNext;
delete pNode->m_pData;
delete pNode;
m_Size--;
return true;
}
template<typename T>
bool sList<T>::remove_last()
{
Node<T>* plastNode = m_pHead;
std::lock_guard<std::mutex> lg(m_ListMutex);
if (getSize() > 1)
{
while (plastNode->m_pNext->m_pNext != nullptr)
plastNode = plastNode->m_pNext;
Node<T>* pNode = plastNode->m_pNext;
plastNode->m_pNext = nullptr;
delete pNode->m_pData;
delete pNode;
pNode->m_pData = pNode = nullptr;
m_Size--;
}
else if(getSize() == 1) // Only 1 node
{
delete m_pHead->m_pData;
delete m_pHead;
m_pHead->m_pData = m_pHead = nullptr;
m_Size--;
}
else // No node available
{
//Nothing to do
}
return true;
}
template<typename T>
bool sList<T>::remove(T val)
{
bool ret = false;
Node<T>* pNode = m_pHead;
Node<T>* pNodeNext = pNode->m_pNext;
if (pNode->m_pData == val)
{
ret = remove_front();
}
else if (pNodeNext->m_pData == val)
{
pNode->m_pNext = pNodeNext->m_pNext;
pNodeNext->m_pNext = nullptr;
delete pNodeNext->m_pData;
delete pNodeNext;
pNodeNext->m_pData = pNodeNext = nullptr;
ret = true;
m_Size--;
}
else
{
while (pNodeNext->m_pData != val)
{
pNode = pNodeNext;
pNodeNext = pNodeNext->m_pNext;
}
if (pNodeNext == nullptr)
ret = false;
else
{
pNode->m_pNext = pNodeNext->m_pNext;
pNodeNext->m_pNext = nullptr;
delete pNodeNext->m_pData;
delete pNodeNext;
pNodeNext->m_pData = pNodeNext = nullptr;
m_Size--;
ret = true;
}
}
return ret;
}
template<typename T>
void sList<T>::traverse()
{
m_pCurrentNode = m_pHead;
while (m_pCurrentNode->m_pNext != nullptr)
{
std::cout << *m_pCurrentNode->m_pData<<" ";
m_pCurrentNode = m_pCurrentNode->m_pNext;
}
std::cout << *m_pCurrentNode->m_pData;
std::cout << std::endl;
}
template<typename T>
void sList<T>::traverse_reverse()
{
m_pCurrentNode = m_pHead;
traverse_reverse(m_pCurrentNode);
std::cout << std::endl;
}
}
#include "MyDS.h"
int main()
{
MyDS::sList<int> myList;
for(int i = 0; i <= 3987; ++i)
myList.insert_front(i);
myList.traverse_reverse(); //Recursion
// myList.traverse();
return 0;
}
As the other answers have pointed out, you have not provided the full code. That said, guessing from the code you have given, I believe that the issue is that you are right about the stack overflow occurring due to too many function calls on the stack when the list of elements is sufficiently long.
In general, it is best to avoid having lots and lots of function calls on the stack. Increasing the stack size is most often not a good solution. See for instance why is stack memory size so limited? for some discussions on this topic.
A single-linked list might be difficult reg. this. One option might be to reverse the single-linked list (maybe creating a new single-linked list), and then just traversing that one (possibly deleting the created list afterwards). A double-linked list would be able to do it very easily and efficiently, since you can just find the last element and then go backwards from there.
If you want to avoid stack overflow, don't use recursion. a simple while can do same job without requiring further resources from stack:
template<typename T>
void sList<T>::traverse_reverse(Node<T>* pNode)
{
while (pNode != nullptr){
std::cout << *pNode->m_pData << " ";
pNode=pNode->m_pNext;
}
}
To increase stack size: Increase stack size in c++
However, in case the above code does not work, I suspect your problem is elsewhere. my initial guess is that you have an infinite loop (in recursion). Let's say for some reason, you have circular dependencies in your list: every node has his m_pNext filled with something other then nullptr. Recursion will never end, hence stackoverflow error. The code above will not work.
Usually circular dependencies arise from incorrect implementation of insert or remove methods. If for some reason you update your pointer in removal incorrectly to another node, it can cause circular dependency.
You can use following code to check for circular dependencies:
template<typename T>
void sList<T>::traverse_reverse(Node<T>* pNode)
{
Node<T>* org_p=pNode;
while (pNode->m_pNext != nullptr){
pNode=pNode->m_pNext;
if(org_p==pNode){
std::cout << "Circular Dependency";
break;
}
}
std::cout << "No Circular Dependency";
}
For printing values in reverse order, I implemented traverse_reverse() function using recursion.
Recursion (unless optimized by your compiler as tail-recursive calls) always consume call stack space. See also this draft report for examples of interesting GCC optimizations. Probably, your C++ compiler is capable of doing similar optimizations.
You could instead prefer consuming heap space, e.g. use intermediate standard C++ containers to keep temporary data.
You could be interested by continuation-passing style. Sometimes it enables you to avoid recursion (and use more heap memory instead).
You can find C++ implementations (recent GCC or Clang comes to mind...) whose source code of std::vector or std::list is open source and readable. You might be surprised by their complexity, related to the rule of five.
If you compiled your C++ code with a recent GCC, you could have used g++ -Wall -Wextra -g -fstack-protector -Wstack-usage=2048 perhaps combined with -O2 to be warned of large call frames.
You might be interested in static source program analysis tools such as Frama-C++, Coverity, Clang static analyzer, or the address sanitizer, or in writing your own GCC plugin to build a call graph and sometimes detect potential stack overflows (but be aware of Rice's theorem). See also valgrind.

c++ how to make lock free stack push atomic

I need to write a void push(const T& val) implementation for lock free stack.
The problem is that compare_exchange_weak expects non atomic node* but I must use std::atomic<node*> next field instead of regular node* next.
I tried to solve this problem by doing this
void push(const T& val) {
node* new_node = new node(val);
node* local_next = new_node->next.load();
while (!head.compare_exchange_weak(local_next, new_node));
}
But creating if local_next makes things even worse. I tested 2 variants of code. The first one has non-atomic field node* next and I lost about 20-30 elements in the test code below. And using the second variant I got a deadlock.
Test code:
#include <iostream>
#include <thread>
#include <atomic>
#include "lock_free_stack.h"
using namespace std;
void test(lock_free_stack<int>& st, atomic<int>& sum) {
st.push(1);
shared_ptr<int> val(st.pop());
while (val == nullptr) { }
sum.store(sum.load() + *val);
}
int main(int argc, const char * argv[]) {
atomic<int> sum;
sum.store(0);
for (int i = 0; i < 100; ++i) {
lock_free_stack<int> st;
thread t1(test, ref(st), ref(sum));
thread t2(test, ref(st), ref(sum));
thread t3(test, ref(st), ref(sum));
thread t4(test, ref(st), ref(sum));
thread t5(test, ref(st), ref(sum));
thread t6(test, ref(st), ref(sum));
thread t7(test, ref(st), ref(sum));
thread t8(test, ref(st), ref(sum));
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
t6.join();
t7.join();
t8.join();
}
if (sum.load() == 800) {
cout << "CORRECT" << endl;
} else {
cout << "TIME TO REWRITE STACK " << sum << endl;
}
return 0;
}
And the code of my lock free stack (first variant):
#ifndef lock_free_stack_hard_lock_free_stack_h
#define lock_free_stack_hard_lock_free_stack_h
template <typename T>
class lock_free_stack {
private:
struct node {
node* next;
std::shared_ptr<T> value;
node (const T& val) : value(std::make_shared<T>(val)) { }
};
std::atomic<node*> head;
std::shared_ptr<T> default_value;
public:
lock_free_stack() : head(nullptr), default_value(std::make_shared<T>()) { }
void push(const T& val) {
node* new_node = new node(val);
new_node->next = head.load();
while (!head.compare_exchange_weak(new_node->next, new_node));
}
std::shared_ptr<T> pop() {
node* old_head = head.load();
while (old_head && !head.compare_exchange_weak(old_head, old_head->next));
if (old_head) {
return old_head->value;
} else {
return std::shared_ptr<T>();
}
}
};
#endif
And the second variant:
#ifndef lock_free_stack_hard_lock_free_stack_h
#define lock_free_stack_hard_lock_free_stack_h
template <typename T>
class lock_free_stack {
private:
struct node {
std::atomic<node*> next;
std::shared_ptr<T> value;
node (const T& val) : value(std::make_shared<T>(val)) { }
};
std::atomic<node*> head;
std::shared_ptr<T> default_value;
public:
lock_free_stack() : head(nullptr), default_value(std::make_shared<T>()) { }
void push(const T& val) {
node* new_node = new node(val);
new_node->next = head.load();
node* local_next = new_node->next.load();
while (!head.compare_exchange_weak(local_next, new_node));
}
std::shared_ptr<T> pop() {
node* old_head = head.load();
while (old_head && !head.compare_exchange_weak(old_head, old_head->next));
if (old_head) {
return old_head->value;
} else {
return std::shared_ptr<T>();
}
}
};
#endif
So the final question is how to create that local_next correctly?
Thank you.
One problem with the test is the line sum.store(sum.load() + *val);
Use atomic ops, such as sum += *val;
The first variant is garbage, because you can't guarantee atomicity on stores to node::next pointer. It would possible to use memory fence/barrier with non-atomic next-pointers, but I wouldn't trust such implementation.
Your second variant is more close to correct implementation.
However you forgot the most important thing from the push() CAS-loop:
void push(const T& val) {
node* new_node = new node(val);
new_node->next = head.load();
node* local_next = new_node->next.load();
while (!head.compare_exchange_weak(local_next, new_node));
}
Here the code allocates new node, loads and stores head pointer to new_node->next. Next, code saves already known stack head pointer value to local_next. (unnecessary step) Then code tries update stack head to new_node without updating new_node->next. This would be fine you were on singe core machine running single thread without pre-emption and the CAS would succeed 100% of time. ;)
When the CAS fails, it loads current fresh value of head into local_next and the code is stuck in infinite loop, because new_node will never be equal to local_next. So you got the last part wrong.
To make a functional CAS-loop the failing thread must reload and recompute what ever data it was trying to update. This means you must update new_node->next from head before re-trying the CAS.
This does not solve the ABA problem of CAS-loops, but I leave it out from my answer. I suggest reading more about CAS-operation and its pitfalls.
Because CAS operation does a load operation the fix is very simple, just store local_next to new_node->next after failing CAS.
Here is more valid (untested) version:
node* new_node = new node(val);
node* local_head = head.load();
new_node->next.store(local_head);
while(!head.compare_exchange_weak(local_head, new_node) {
new_node->next.store(local_head);
}
You'll to do similiar thing to your pop() implementation.

C++11 lockless queue using std::atomic (multi writer, single consumer)

I've produced a simple implementation of the lockless (lockfree) queue using the new std::atomic in C++11. I can't see what I'm doing wrong here.
#include <atomic>
template<typename T>
class lockless_queue
{
public:
template<typename DataType>
struct node
{
node(const DataType& data)
: data(data), next(nullptr) {}
DataType data;
node* next;
};
lockless_queue()
: head_(nullptr) {}
void produce(const T &data)
{
node<T>* new_node = new node<T>(data);
// put the current value of head into new_node->next
new_node->next = head_.load(std::memory_order_relaxed);
// now make new_node the new head, but if the head
// is no longer what's stored in new_node->next
// (some other thread must have inserted a node just now)
// then put that new head into new_node->next and try again
while(!std::atomic_compare_exchange_weak_explicit(
&head_,
&new_node->next,
new_node,
std::memory_order_release,
std::memory_order_relaxed)) {}
}
node<T>* consume_all()
{
// Reset queue and return head atomically
return head_.exchange(nullptr, std::memory_order_consume);
}
private:
std::atomic<node<T>*> head_;
};
// main.cpp
#include <iostream>
int main()
{
lockless_queue<int> s;
s.produce(1);
s.produce(2);
s.produce(3);
auto head = s.consume_all();
while (head)
{
auto tmp = head->next;
std::cout << tmp->data << std::endl;
delete head;
head = tmp;
}
}
And my output:
2
1
Segmentation fault (core dumped)
Can I have some pointers where to look or an indication what I could be doing wrong?
Thanks!
You are dereferencing tmp instead of head:
while (head)
{
auto tmp = head->next;
std::cout << tmp->data << std::endl;
delete head;
head = tmp;
}
should be:
while (head)
{
std::cout << head->data << std::endl;
auto tmp = head->next;
delete head;
head = tmp;
}
This is why 3 doesn't appear in your output and Segmentation fault does.
You have another error in your code that won't show up until you start trying to perform concurrent enqueues. If your compare_exchange_weak_explicit fails, that implies that another thread managed to change the head pointer, and as such before you can try your CAS again, you need to re-load the new value of the head pointer into your new_node->next. The following will do the trick:
while(!std::atomic_compare_exchange_weak_explicit(
&head_,
&new_node->next,
new_node,
std::memory_order_release,
std::memory_order_relaxed)) {
new_node->next = head_.load(std::memory_order_relaxed);
}