HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Single word CAS tagged pointer for algorithms susceptible to the ABA problem

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
problemthetaggedabaalgorithmscaspointerwordsinglefor

Problem

I've been looking for a solution to the ABA problem for a lock-free stack. Searching the Web revealed patented and complicated hazard pointers and tagged pointers using double compare-and-swap (DCAS, CAS2). I would settle for the DCAS, but it's not available on some older AMD CPUs. So I came up with this helper class:

class TaggedPtrBase {
    typedef unsigned int tag_t;
    static const tag_t INVALIDATED = 1;
public:
    TaggedPtrBase() {
        tag.store(0, std::memory_order_relaxed);
        ptr = 0;
    }
    TaggedPtrBase & operator=(const TaggedPtrBase & other) {
        tag.store(other.tag.load(std::memory_order_acquire), std::memory_order_relaxed);
        ptr = other.ptr;
        return *this;
    }
    operator void *() const {
        return get();
    }
    void * get() const {
        return ptr;
    }
    bool compare_and_swap(const TaggedPtrBase & oldval, void * newptr) {
        uintptr_t old_tag = oldval.tag.load(std::memory_order_relaxed);
        if (old_tag & INVALIDATED) {
            return false;
        }
        if (tag.compare_exchange_strong(old_tag, old_tag | INVALIDATED, std::memory_order_acquire)) {
            ptr = newptr;
            tag.store(old_tag + 2, std::memory_order_release);
            return true;
        } else {
            return false;
        }
    }
private:
    std::atomic tag;
    void * ptr;
};
...
void push(Node * node) {
    void * newptr = reinterpret_cast(node);
    TaggedPtrBase old;
    do {
        old = ptr;
        node->next = static_cast(old.get());
    } while (!ptr.compare_and_swap(old, newptr));
}


It appears to be working, but it's simplicity suggests that I'm missing something.
So I wonder if this is a proper solution, or is it just a glorified spin-lock? Any issues on non-x86 platforms?

Solution

It's just a spin lock. If the thread that invalidated of the tag got suspended right after, no other thread can make progress till the thread is resumed and releases the tag.

if (old_tag & INVALIDATED) {
    return false; // no thread can go further if ...
}
if (tag.compare_exchange_strong(old_tag, old_tag | INVALIDATED, 
                                std::memory_order_acquire)) {
    ptr = newptr; // <-- ... a thread is preempted here
    tag.store(old_tag + 2, std::memory_order_release);
    return true;
}


So the solution is not lock-free.

Code Snippets

if (old_tag & INVALIDATED) {
    return false; // no thread can go further if ...
}
if (tag.compare_exchange_strong(old_tag, old_tag | INVALIDATED, 
                                std::memory_order_acquire)) {
    ptr = newptr; // <-- ... a thread is preempted here
    tag.store(old_tag + 2, std::memory_order_release);
    return true;
}

Context

StackExchange Code Review Q#4706, answer score: 2

Revisions (0)

No revisions yet.