I've been thinking of how to implement a lock-free singly linked list. And to be honest, I don't see many bullet proof ways to do it. Even the more robust ways out there that use CAS
end up having some degree of the ABA problem.
So I got to thinking. Wouldn't a partially lock-free system be better than always using locks? May some operations can be atomic and lock-free? If I can do that, it should still be thread safe.
So, onto the question. I am thinking of a simple singly linked list. 2 main operations. push
and pop
. push
always inserts at the front. Something like this:
void push(int n) {
T *p = new T;
p->n = n;
p->next = root;
root = p;
}
And pop which always takes the first element. Something like this:
T *pop() {
T *p = root;
root = root->next;
return p;
}
Obviously push is non-trivial enough that a simple lock-free approach is probably not going to happen. But pop looks maybe doable. Using gcc-intrinsics I've thought of this:
T *pop() {
return __sync_lock_test_and_set(&root, root->next);
}
Functionally equivalent? yup. Lock-free? yup. Thread Safe? I Don't Know. My gut reaction is no, and here's why.
I am concerned about the fact that one of the parameters to the test_and_set
has to dereference memory. What if root changes between the root->next
and the call to __sync_lock_test_and_set
.
I suppose that this code is equivalent to this:
T *pop() {
T *temp = root->next;
// are we broken if a push/pop happens here?
return __sync_lock_test_and_set(&root, temp);
}
So, like I said, I think that this code is not correct. But can anyone say for certain that I am drawing the right conclusions (I'd hate to write off something that works nicely). If it is in fact broken as I suspect. Is there any simple solution?