c++ how to make lock free stack push atomic - c++

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.

Related

Is this usage of condition variable wrong?

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?

Dereferencing node pointer in my C++ linked list implementation giving unexpected results

I'm implementing an unordered linked list in C++ based on first principles. My (partially complete so far) implementation is:
#include <iostream>
class Node {
int m_data;
Node *m_next;
public:
Node(int data)
{
m_data = data;
m_next = nullptr;
}
int getData() { return m_data; }
void setData(int data) { m_data = data; }
Node* getNext() { return m_next; }
void setNext(Node *next) { m_next = next; }
};
class UnorderedList {
public:
Node *m_head;
public:
UnorderedList()
{
m_head = nullptr;
}
bool isEmpty() { return m_head == nullptr; }
void appendToHead(int data)
{
Node temp = Node(data);
temp.setNext(m_head);
m_head = &temp;
}
void remove(int data);
bool search(int data);
};
int main()
{
UnorderedList list1;
list1.appendToHead(32);
list1.appendToHead(47);
list1.appendToHead(90);
std::cout << list1.m_head->getData() << '\n';
std::cout << list1.m_head->getNext()->getData() << '\n';
return 0;
}
I am able to correctly print the head of the list as '90', but the next line (i.e. getNext()->getData()) gets printed as a large random number (281314120). What is the reason for this?
void appendToHead(int data)
{
Node temp = Node(data);
temp.setNext(m_head);
m_head = &temp;
}
Never store the address of an object with automatic storage duration. That object (in this case, temp) will cease to exist when appendToHead method completes.
You did that and invoked Undefined Behavior. You probably wanted to do:
void appendToHead(int data)
{
Node* temp = new Node(data);
temp->setNext(m_head);
m_head = temp;
}
You should also consider satisfying The Rule Of Five
Rather than raw pointers, additionally explore the use of std::unique_ptr.

Segfault when appending node to linked list

I've been trying to rewrite some basic data structures using C++ to refresh my memory on some of the basics of OOP, but I've run into a silly problem already.
I'm trying to build a singly linked list, append the strings "Hello" and "World" to the list, and then view all of the contents within the list. This is a very easy task, but I'm getting a segmentation fault when I run the following code:
driver.cc
#include <iostream>
#include <string>
#include "SinglyLinkedList.h"
int main()
{
SLL<std::string> List;
List.Append("Hello");
List.Append("World");
List.visitAll(std::cout);
return 0;
}
Node.h
#ifndef NODE_H
#define NODE_H
template <class T>
class Node {
public:
Node<T>() {}
Node<T>(T init) { data = init; next = nullptr; }
void setData(T newData) { data = newData; }
void setNext(Node<T> *nextNode) { next = nextNode; }
const T getData() { return data; }
Node<T> *getNext() { return next; }
private:
T data;
Node<T> *next;
};
#endif
SinglyLinkedList.h
#ifndef SINGLY_LINKEDLIST_H
#define SINGLY_LINKEDLIST_H
#include "Node.h"
#include <iostream>
template <class T>
class SLL {
public:
SLL<T>() { head = nullptr; size = 0; }
~SLL<T>() {}
void Append(T added);
void Delete(T deleted);
void visitAll(std::ostream &outs);
private:
Node<T> *head;
long size;
};
template <class T>
void SLL<T>::Append(T added)
{
Node<T> *newNode = new Node<T>(added);
Node<T> *temp = head;
if(temp != nullptr) {
while(temp != nullptr) {
temp = temp->getNext();
}
temp->setNext(newNode); // seg fault here
}
else {
head = newNode;
}
}
template <class T>
void SLL<T>::visitAll(std::ostream &outs)
{
Node<T> *temp = head;
while(temp)
{
outs << temp->getData() << std::endl;
temp=temp->getNext();
}
}
#endif
Just debugging by hand, I create a new node with data = "Hello" and next = nullptr. This gets appended by the else in the void SLL<T>::Append method because temp == nullptr. However, on the second Append, the while loop runs once, then crashes when calling the setter of the Node class. I cannot figure out why this is the case.
I'm expecting to see
Hello
World
Am I being too tunnel-visioned? This is pretty silly. Sorry if it's too basic for SO...
Thanks,
erip
while(temp != nullptr) {
temp = temp->getNext();
}
temp->setNext(newNode); // seg fault here
That's because you are breaking out of the while loop when temp == nullptr.
Use:
while(temp->getNext() != nullptr) {
temp = temp->getNext();
}
temp->setNext(newNode);
Your while loop in Append ends with temp being a null pointer, therefore no temp->setNext()

Interwoven threads affecting linked list

I am told that the code in insert() isn't thread-safe because an interwoven thread can set head to node after another thread does, effectively loosing a link to one node. But no matter how many times I run this program I keep getting 2 as the number of nodes instead of 1. Why is that?
#include <functional>
#include <iostream>
#include <thread>
#include <chrono>
struct List
{
struct Node
{
int data{0};
Node* next{0};
};
Node* head{0};
void insert(int n)
{
Node* node = new Node{n};
node->next = head;
head = node;
}
};
int Count(List& list)
{
int count = 0;
for (List::Node* head = list.head; head != nullptr; head = head->next)
count++;
return count;
}
int main()
{
List i;
std::thread t1(&List::insert, &i, 5);
std::thread t2(&List::insert, &i, 3);
t1.join();
t2.join();
std::cout << Count(i);
}
Demo

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);
}