无锁堆栈:正确使用内存顺序是什么?

时间:2018-11-07 13:13:19

标签: c++ c++11 atomic lock-free memory-barriers

下面的类描述了uint32_t个顺序值(完整代码here)的无锁堆栈。例如,LockFreeIndexStack stack(5);声明一个包含数字{0, 1, 2, 3, 4}的堆栈。此类具有池语义。堆栈的容量是固定的。只能提取并重新插入最初引入堆栈的值。因此,在任何特定时间点,这些值中的任何一个都可以位于堆栈内部或外部,但不能同时位于两者之间。线程只能推送它先前通过弹出窗口获得的索引。因此,正确的用法是让线程执行此操作:

auto index = stack.pop(); // get an index from the stack, if available
if(index.isValid()) {
    // do something with 'index'
    stack.push(index); // return index to the stack
}

pushpop方法都是通过原子loadCAS循环实现的。

poppush(我把我的猜测写出来了)中的原子操作中应该使用的正确的存储顺序语义是什么?

struct LockFreeIndexStack
{
    typedef uint64_t bundle_t;
    typedef uint32_t index_t;

private:
    static const index_t s_null = ~index_t(0);

    typedef std::atomic<bundle_t> atomic_bundle_t;

    union Bundle {
        Bundle(index_t index, index_t count)
        {
            m_value.m_index = index;
            m_value.m_count = count;
        }

        Bundle(bundle_t bundle)
        {
            m_bundle = bundle;
        }

        struct {
            index_t m_index;
            index_t m_count;
        } m_value;

        bundle_t m_bundle;
    };

public:
    LockFreeIndexStack(index_t n)
        : m_top(Bundle(0, 0).m_bundle)
        , m_next(n, s_null)
    {
        for (index_t i = 1; i < n; ++i)
            m_next[i - 1] = i;
    }

    index_t pop()
    {
        Bundle curtop(m_top.load());  // memory_order_acquire?
        while(true) {
            index_t candidate = curtop.m_value.m_index;

            if (candidate != s_null) {  // stack is not empty?
                index_t next = m_next[candidate];
                Bundle newtop(next, curtop.m_value.m_count);
                // In the very remote eventuality that, between reading 'm_top' and
                // the CAS operation other threads cause all the below circumstances occur simultaneously:
                // - other threads execute exactly a multiple of 2^32 pop or push operations,
                //   so that 'm_count' assumes again the original value;
                // - the value read as 'candidate' 2^32 transactions ago is again top of the stack;
                // - the value 'm_next[candidate]' is no longer what it was 2^32 transactions ago
                // then the stack will get corrupted
                if (m_top.compare_exchange_weak(curtop.m_bundle, newtop.m_bundle)) {
                    return candidate;
                }
            }
            else {
                // stack was empty, no point in spinning
                return s_null;
            }
        }
    }

    void push(index_t index)
    {
        Bundle curtop(m_top.load());    // memory_order_relaxed?
        while (true) {
            index_t current = curtop.m_value.m_index;
            m_next[index] = current;
            Bundle newtop = Bundle(index, curtop.m_value.m_count + 1);
            if (m_top.compare_exchange_weak(curtop.m_bundle, newtop.m_bundle)) {
                return;
            }
        };
    }

private:
    atomic_bundle_t m_top;
    std::vector<index_t> m_next;
};

0 个答案:

没有答案