简体   繁体   English

无锁双链表的原子操作

[英]Atomic operations for lock-free doubly linked list

I am writing a lock-free doubly linked list based on these papers: 我根据这些文件编写了无锁双向链表:

"Efficient and Reliable Lock-Free Memory Reclamation Based on Reference Counting" Anders Gidenstam,Member, IEEE,Marina Papatriantafilou, H˚ akan Sundell and Philippas Tsigas “基于引用计数的高效可靠的无锁内存回收”,Anders Gidenstam,成员,IEEE,Marina Papatriantafilou,H˚akan Sundell和Philippas Tsigas

"Lock-free deques and doubly linked lists" Håkan Sundell, Philippas Tsigas “无锁双端队列和双链表”,Philippas Tsigas,HåkanSundell

For this question we can put aside first paper. 对于这个问题,我们可以搁置第一篇论文。

In this paper, they use a smart way for storing a deletion flag and a pointer in a word. 在本文中,他们使用一种聪明的方式在单词中存储删除标记和指针。 (More info here ) (更多信息在这里

Pseudo code for this section in the paper: 本文中此部分的伪代码:

union Link
    : word
    (p,d): {pointer to Node, boolean} 

structure Node
    value: pointer to word
    prev: union Link
    next: union Link

And my code for above pseudo code: 而我的上述伪代码的代码:

template< typename NodeT >
struct LockFreeLink
{
public:
    typedef NodeT NodeType;

private:

protected:
    std::atomic< NodeT* > mPointer;

public:
    bcLockFreeLink()
    {
        std::atomic_init(&mPointer, nullptr);
    }
    ~bcLockFreeLink() {}

    inline NodeType* getNode() const throw()
    {
        return std::atomic_load(&mPointer, std::memory_order_relaxed);
    }
    inline std::atomic< NodeT* >* getAtomicNode() const throw()
    {
        return &mPointer;
    }
};

struct Node : public LockFreeNode
{
    struct Link : protected LockFreeLink< Node >
    {
        static const int dMask = 1;
        static const int ptrMask = ~dMask;

        Link() { } throw()
        Link(const Node* pPointer, bcBOOL pDel = bcFALSE) throw()
        { 
            std::atomic_init(&mPointer, (reinterpret_cast<int>(pPointer) | (int)pDel)); 
        }

        Node* pointer() const throw() 
        { 
            return reinterpret_cast<Node*>(
                std::atomic_load(&data, std::memory_order_relaxed) & ptrMask); 
        }
        bool del() const throw() 
        { 
            return std::atomic_load(&data, std::memory_order_relaxed) & dMask; 
        }
        bool compareAndSwap(const Link& pExpected, const Link& pNew) throw() 
        { 
            Node* lExpected = std::atomic_load(&pExpected.mPointer, std::memory_order_relaxed);
            Node* lNew = std::atomic_load(&pNew.mPointer, std::memory_order_relaxed);

            return std::atomic_compare_exchange_strong_explicit(
                &mPointer,
                &lExpected,
                lNew,
                std::memory_order_relaxed,
                std::memory_order_relaxed); 
        }

        bool operator==(const Link& pOther) throw() 
        { 
            return std::atomic_load(data, std::memory_order_relaxed) == 
                std::atomic_load(pOther.data, std::memory_order_relaxed); 
        }
        bool operator!=(const Link& pOther) throw() 
        { 
            return !operator==(pOther); 
        }
    };

    Link mPrev;
    Link mNext;
    Type mData;

    Node() {};
    Node(const Type& pValue) : mData(pValue) {};
};

In this paper there is this function for set deletion mark of link to true: 本文提供了用于将链接的删除标记设置为true的功能:

procedure SetMark(link: pointer to pointer to Node)
    while true do
       node = *link;
       if node.d = true or CAS(link, node, (node.p, true)) then break;

And my code for this function: 我的函数代码:

void _setMark(Link* pLink)
{
    while (bcTRUE)
    {
        Link lOld = *pLink;
        if(pLink->del() || pLink->compareAndSwap(lOld, Link(pLink->pointer(), bcTRUE)))
            break;
    }
}

But my problem is in compareAndSwap function where i must compare and swap three atomic variable. 但是我的问题是在compareAndSwap函数中,我必须比较并交换三个原子变量。 Information about problem is here 有关问题的信息在这里

(Actually new variable in compare and swap function isn't important because it is thread local) (实际上,比较和交换功能中的new变量并不重要,因为它是线程局部的)

Now my question: how can i write compareAndSwap function to compare and swap three atomic varialbe or where am i making mistake? 现在我的问题是:我该如何编写compareAndSwap函数来比较和交换三个原子变量,或者我在哪里出错?

(Excuse me for long question) (对不起,我想问很久)

Edit: 编辑:

similar problem is in memory manager paper: 内存管理器文件中也存在类似的问题:

function CompareAndSwapRef(link:pointer to pointer toNode,
old:pointer toNode, new:pointer toNode):boolean
    if CAS(link,old,new) then
        if new=NULL then
            FAA(&new.mmref,1);
            new.mmtrace:=false;
    if old=NULLthen FAA(&old.mmref,-1);
    return true;
return false; 

here again i must compare and swap three atomic variable. 在这里我必须再次比较并交换三个原子变量。 (Note that my arguments are type of Link and i must compare and swap mPointer of Link ) (请注意,我的参数是Link类型,我必须比较并交换Link mPointer

Unless you can make your three data items that you are comparing/swapping fit into two pointer-size elements, you can't do this with compare and swap (certainly not on x86, and I've not heard of any other machine architecture that has such a thing). 除非您可以将要比较/交换的三个数据项放到两个指针大小的元素中,否则不能通过比较和交换来做到这一点(肯定不是在x86上,而且我还没有听说过任何其他机器架构有这样的事情)。

If you rely on the data being stored on an address that is (at least) aligned to an even byte-address, you could potentially use bitwise OR to set the lowest bit when deleting the element. 如果您依赖存储在(至少)与偶数字节地址对齐的地址上的数据,则在删除元素时可能会使用按位或来设置最低位。 In the past, people have been using the upper parts of the address to store extra data, but in x86-64 at least, this is not possible, as the upper part of the address must be "canonical", meaning that any address bits above the "usable limit" (defined by the processor architecture, currently this is 48 bits), must all be the same as the highest bit of the usable limit (so, same as bit 47). 过去,人们一直在使用地址的上部存储额外的数据,但是至少在x86-64中,这是不可能的,因为地址的上部必须是“规范的”,这意味着任何地址位高于“可用限制”(由处理器体系结构定义,当前为48位),必须全部与可用限制的最高位相同(因此与第47位相同)。

Edit: This section of code does exactly what I describe: 编辑:这部分代码完全符合我的描述:

    static const int dMask = 1;
    static const int ptrMask = ~dMask;

    Link() { } throw()
    Link(const Node* pPointer, bcBOOL pDel = bcFALSE) throw()
    { 
        std::atomic_init(&mPointer, (reinterpret_cast<int>(pPointer) | (int)pDel)); 
    }

    Node* pointer() const throw() 
    { 
        return reinterpret_cast<Node*>(
            std::atomic_load(&data, std::memory_order_relaxed) & ptrMask); 
    }

It uses the lowest bit to store the pDel flag. 它使用最低位来存储pDel标志。

You should be able to do this for a double-linked list by using the a form of cmpxchg16b (on x86). 通过使用cmpxchg16b的形式(在x86上),您应该能够对双向链接列表执行此操作。 In a Windows system, that would be the _InterlockedCompareExchange128 . 在Windows系统中,该_InterlockedCompareExchange128 In gcc (for Unix type OS's, such as Linux/MacOS) you will need to first construct a int128 from your two pointers. 在gcc中(对于Unix类型的OS,例如Linux / MacOS),您需要首先根据两个指针构造一个int128 If you are compiling for 32-bit code, you will probably need to make a 64-bit int for both Windows and Unix OS's. 如果要编译32位代码,则可能需要为Windows和Unix OS都制作一个64位int。

http://www.drdobbs.com/cpp/lock-free-code-a-false-sense-of-security/210600279 http://www.drdobbs.com/cpp/lock-free-code-a-false-sense-of-security/210600279

But replacing locks wholesale by writing your own lock-free code is not the answer. 但是,通过编写自己的无锁代码来批量替换锁不是答案。 Lock-free code has two major drawbacks. 无锁代码有两个主要缺点。 First, it's not broadly useful for solving typical problems—lots of basic data structures, even doubly linked lists, still have no known lock-free implementations . 首先,它对于解决典型问题并没有广泛的用途-许多基本数据结构, 甚至双链表,仍然没有已知的无锁实现 Coming up with a new or improved lock-free data structure will still earn you at least a published paper in a refereed journal, and sometimes a degree. 提出一种新的或改进的无锁数据结构仍将使您至少获得一份在推荐期刊上发表的论文,有时甚至是学位。

I don't think it would be efficient enough to use it, but anyway it's interesting to read. 我认为使用它的效率不够高,但是无论如何它还是很有趣的。

On x64, only 44 bits of address space are used. 在x64上,仅使用44位地址空间。 If your pointers are aligned to 8 bytes then you are only using 41 bits. 如果您的指针对齐8个字节,则您仅使用41位。 41x2 is still too large for 64 bits. 对于64位,41x2仍然太大。 There is a 128 bit compare and swap although I can't vouch for its speed. 尽管我不能保证它的速度,但是有一个128位的比较和交换。 I always try to use the 64 bit one. 我总是尝试使用64位之一。

Maybe you only need up to 2 billion nodes. 也许您只需要多达20亿个节点。 So what you could do is preallocate a pool of nodes that the list pulls from. 因此,您可以做的是预分配列表所来自的节点池。 You create nodes by grabbing the next free pool index using atomic ops of course. 您可以通过使用原子操作获取下一个空闲池索引来创建节点。 Then instead of next and prev being pointers, they could be 31 bit indexes into the node pool and you have 2 bits left over for delete flags. 然后,它们可能不是节点指针的下一个和上一个指针,而是节点池中的31位索引,您还有2位用于删除标志。 Assuming you don't need 2 billion nodes, you have even more bits left over. 假设您不需要20亿个节点,那么您将剩下更多的位。 The only downside is you have to know how many nodes you are going to need at startup, although you could realloc the nodes if you had too. 唯一的缺点是您必须知道启动时需要多少个节点,尽管您也可以重新分配节点。

What I have done is used virtual memory functions to reserve GB of address space and then map physical ram into that space as I need it to extend my pool without having to reallocate. 我所做的是使用虚拟内存功能来保留GB的地址空间,然后根据需要将物理内存映射到该内存中,以扩展我的池而不必重新分配。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM