I've written a toy example of the concurrent stack which has only three functions push()
, peek()
, and length()
. I've used atomic variables for synchronization. Is there anything incorrect in terms of synchronization? Could you please review this code?
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
template <typename T>
struct Node {
Node<T>* next{};
T val{};
Node(const T& v) : val(v) {};
};
template <typename T>
class Stack {
std::atomic<Node<T>*> top{};
std::atomic<size_t> size{};
public:
void push(const T& data) {
Node<T>* node = new Node<T>(data);
size_t length = size.load(std::memory_order_relaxed);
node->next = top.load(std::memory_order_relaxed);
while(!std::atomic_compare_exchange_weak_explicit(&top, &node->next, node, std::memory_order_release, std::memory_order_relaxed));
while(!std::atomic_compare_exchange_weak_explicit(&size, &length, size + 1, std::memory_order_release, std::memory_order_relaxed));
}
Node<T>* peek() {
return top.load();
}
size_t length() const {
return size;
}
};
template <typename T>
class Runner {
int cnt{};
Stack<T>* ss{};
public:
Runner(const int n, Stack<T>* s) : cnt(n), ss(s) {}
void operator()() {
for(int i = 0; i < cnt; ++i) {
size_t size = ss->length();
if(size >= cnt) {
return;
}
ss->push(i);
}
}
Stack<T>* get_stack() const {
return ss;
}
};
int main() {
std::vector<std::thread> threads{};
Stack<int>* s = new Stack<int>();
int n = 10;
Runner<int> r(n, s);
for(int i = 0; i < 3; ++i) {
threads.push_back(std::move(std::thread(r)));
}
for(int i = 0; i < 3; ++i) {
threads[i].join();
}
Stack<int>* top = r.get_stack();
Node<int>* head = top->peek();
while(head) {
std::cout << head->val << ", ";
head = head->next;
}
size_t size = top->length();
return 0;
}