-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtrieber_stack.cpp
More file actions
174 lines (155 loc) · 5.69 KB
/
trieber_stack.cpp
File metadata and controls
174 lines (155 loc) · 5.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*****************************************************************
* @author Suraj Ajjampur
* @file trieber_stack.cpp
*
* @brief This C++ source file implements Trieber stack which is a
* non-blocking data structure. It is linearizable and lock-free
*
* Garbage Collection Issues currently dealt with
* 1) Race against reclaimation - Done
* 2) ABA problem - To-do
*
* @date 14 Dec 2023
********************************************************************/
#include <cstddef> // for std::uintptr_t
#include "trieber_stack.h"
#define CONTENTION_OPT 1
using namespace std;
/** Pushes the value onto the stack
*
* @param val integer to be added onto the stack
*/
void tstack::push(int val){
// Creating a new node and attempting to push it onto the stack
node* n = new node(val);
node* old_top;
do {
old_top = top.load(ACQUIRE); // Load the current value of top
n->down.store(old_top, RELAXED); // Set the new node's next pointer to the current top
// Attempt to swap the old top with the new top.
// If another thread has modified the top, the CAS will fail and retry.
#if CONTENTION_OPT == 0
} while(!cas(top, old_top, n, ACQ_REL)); // This is the linearization point of push
#else
} while(top.load(ACQUIRE) == old_top && !cas(top, old_top, n, ACQ_REL));
#endif
DEBUG_MSG(val);
}
/** Returns the most recent pushed value - recent is defined by linearizability
*
* @return The value of the popped node, or NULL if the stack is empty.
*/
int tstack::pop(void){
node* t;
node* n;
int v;
do {
t = top.load(ACQUIRE); // Load the current value of top
if(t == nullptr){
return -1; // Stack is empty, return NULL
}
n = t->down.load(RELAXED); // Get the next node
v = t->val.load(RELAXED); // Read the value from the current top node
// Attempt to swap the old top with the new top.
// If another thread has modified the top, the CAS will fail and retry.
#if CONTENTION_OPT == 0
} while(!cas(top, t, n, ACQ_REL)); // This is the linearization point of pop
#else
} while(top.load(ACQUIRE) == t && !cas(top, t, n, ACQ_REL));
#endif
// Memory reclamation should be performed here.
//delete t;
// Delete the old node after ensuring no other threads are accessing it.
DEBUG_MSG(v);
return v; // Return the value of the popped node
}
void Push(tstack& stack, int val) {
stack.push(val);
}
void Pop(tstack& stack, std::atomic<int>& popCount) {
int val = stack.pop();
if ( val != -1) {
popCount.fetch_add(1, RELAXED);
}else{DEBUG_MSG("Stack is empty");}
}
/** Test for Treiber Stack where a vector of values are being pushed into the stack
* by multiple threads (numThreads) and being popped out of the stack concurrently
* with a chosen optimization. The test passes if the number of pushes are equal to the
* number of pops.
*
* @param values The values to push onto the stack.
* @param optimization The optimization strategy used (not used in this function).
* @param numThreads The number of threads used for pushing and popping.
*/
void treiber_stack_test(std::vector<int>& values, int numThreads) {
tstack stack;
std::atomic<int> popCount(0);
std::vector<std::thread> threads;
if (numThreads > 1) {
// Concurrent pushes and pops with multiple threads
int halfNumThreads = numThreads / 2;
// Push threads
for (int i = 0; i < halfNumThreads; ++i) {
threads.push_back(std::thread([&stack, &values, i, halfNumThreads]() {
for (int j = i; j < values.size(); j += halfNumThreads) {
Push(stack, values[j]);
}
}));
}
// Wait for all threads to complete
for (auto& t : threads) {
t.join();
}
threads.clear();
// Pop threads
for (int i = 0; i < values.size(); ++i) {
threads.push_back(std::thread([&stack, &popCount]() {
Pop(stack, popCount);
}));
}
} else {
// Single thread performs both push and pop
for (const auto& value : values) {
Push(stack, value);
Pop(stack, popCount);
}
}
// Wait for all threads to complete
for (auto& t : threads) {
t.join();
}
// Check if the number of successful pops matches the number of pushes
if (popCount.load(RELAXED) != values.size()) {
std::cerr << "Error: The number of successful pops does not match the number of pushes." << std::endl;
std::cerr << "Pops: " << popCount << ", Pushes: " << values.size() << std::endl;
} else {
std::cout << "Test for Treiber stack passed" << std::endl;
}
}
void push_pop(void){
/************ Testing for the Trieber Stack here **********/
tstack stack; // Create an instance of tstack
// Test the push function
std::cout << "Pushing then popping alternatively" << std::endl;
stack.push(1);
cout << "Value is " << stack.pop() << endl;
stack.push(2);
cout << "Value is " << stack.pop() << endl;
stack.push(3);
cout << "Value is " << stack.pop() << endl;
}
void push3_pop_till_empty(void){
/************ Testing for the Trieber Stack here **********/
tstack stack; // Create an instance of tstack
// Test the push function
std::cout << "Pushing values onto the stack..." << std::endl;
stack.push(1);
stack.push(2);
stack.push(3);
// Test the pop function
std::cout << "Popping values from the stack till empty..." << std::endl;
int val;
while ((val = stack.pop()) != -1) { // Continue popping until the stack is empty
std::cout << "Popped: " << val << std::endl;
}
}