This is not a general solution.
There is some internal locking going on behind the scene.
CAA basically says the following:
val
to be changed from comp
to nval
nval
to val
, val
is not comp
, return false and fail the assignmentint Lock = OPEN; // shared
bool CAA(int & val, int comp, int nval){
//begin atomic
if(val == comp){
val = nval;
return true;
}
return false;
//end atomic
}
void Task::main(){
while(!CAA(Lock, OPEN, CLOSEd));
// critical section
Lock = OPEN;
}
class Stack{
Node *top;
public:
struct Node{
//data
Node * next;
};
void push(Node & n){
for(;;){
n.next = top;
if(CAA(top, n.next, &n)) break;
}
};
Node * pop(){
Node * t;
for(;;){
t = top;
if(t == nullptr) return t;
if(CAA(top, t, t->next)) return t;
}
};
}
push()
works, but pop()
does not, at least not always.
Consider this to be the stack: top -> A -> B -> C
pop()
would not work if,
pop()
called CAA(top, A, B)
, and gets time sliced.A -> B -> C
-> C
-> A -> C
pop()
routine comes back, and sees that top is still A
, so it thinks nothing has been changed; the routine assignes top to be B
which is popped off.top -> B -> ????
This cannot be fixed easily.
A solution for the problem above is to take advantage of double-wide CAVD instruction.
This compares/assigns 64(or 128) bit values for 32(or 64) bit architectures.
For CAV function itself, everything looks the same except the data types.
But we will include some tickets attached to the node struct.
bool CAVD(uintS_t & val, uintS_t comp, uintS_t nval){
//begin atomic
if(val == comp){
val = nval;
return true;
}
return false;
//end atomic
}
class Stack{
Node *top;
union Link{
struct{ // 32/64 bit data * 2
Node *top; // pointer to top node
uintptr_t count; // ticket
};
uintS_t atom; // raw form of above struct
} link;
public:
struct Node{
//data
Link * next;
};
Stack() {link.atom = 0;}