[英]Atomic operations for lock-free doubly linked list
我根据这些文件编写了无锁双向链表:
“基于引用计数的高效可靠的无锁内存回收”,Anders Gidenstam,成员,IEEE,Marina Papatriantafilou,H˚akan Sundell和Philippas Tsigas
“无锁双端队列和双链表”,Philippas Tsigas,HåkanSundell
对于这个问题,我们可以搁置第一篇论文。
在本文中,他们使用一种聪明的方式在单词中存储删除标记和指针。 (更多信息在这里 )
本文中此部分的伪代码:
union Link
: word
(p,d): {pointer to Node, boolean}
structure Node
value: pointer to word
prev: union Link
next: union Link
而我的上述伪代码的代码:
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) {};
};
本文提供了用于将链接的删除标记设置为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;
我的函数代码:
void _setMark(Link* pLink)
{
while (bcTRUE)
{
Link lOld = *pLink;
if(pLink->del() || pLink->compareAndSwap(lOld, Link(pLink->pointer(), bcTRUE)))
break;
}
}
但是我的问题是在compareAndSwap
函数中,我必须比较并交换三个原子变量。 有关问题的信息在这里
(实际上,比较和交换功能中的new
变量并不重要,因为它是线程局部的)
现在我的问题是:我该如何编写compareAndSwap函数来比较和交换三个原子变量,或者我在哪里出错?
(对不起,我想问很久)
编辑:
内存管理器文件中也存在类似的问题:
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;
在这里我必须再次比较并交换三个原子变量。 (请注意,我的参数是Link
类型,我必须比较并交换Link
mPointer
)
除非您可以将要比较/交换的三个数据项放到两个指针大小的元素中,否则不能通过比较和交换来做到这一点(肯定不是在x86上,而且我还没有听说过任何其他机器架构有这样的事情)。
如果您依赖存储在(至少)与偶数字节地址对齐的地址上的数据,则在删除元素时可能会使用按位或来设置最低位。 过去,人们一直在使用地址的上部存储额外的数据,但是至少在x86-64中,这是不可能的,因为地址的上部必须是“规范的”,这意味着任何地址位高于“可用限制”(由处理器体系结构定义,当前为48位),必须全部与可用限制的最高位相同(因此与第47位相同)。
编辑:这部分代码完全符合我的描述:
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);
}
它使用最低位来存储pDel
标志。
通过使用cmpxchg16b
的形式(在x86上),您应该能够对双向链接列表执行此操作。 在Windows系统中,该_InterlockedCompareExchange128
。 在gcc中(对于Unix类型的OS,例如Linux / MacOS),您需要首先根据两个指针构造一个int128
。 如果要编译32位代码,则可能需要为Windows和Unix OS都制作一个64位int。
http://www.drdobbs.com/cpp/lock-free-code-a-false-sense-of-security/210600279
但是,通过编写自己的无锁代码来批量替换锁不是答案。 无锁代码有两个主要缺点。 首先,它对于解决典型问题并没有广泛的用途-许多基本数据结构, 甚至双链表,仍然没有已知的无锁实现 。 提出一种新的或改进的无锁数据结构仍将使您至少获得一份在推荐期刊上发表的论文,有时甚至是学位。
我认为使用它的效率不够高,但是无论如何它还是很有趣的。
在x64上,仅使用44位地址空间。 如果您的指针对齐8个字节,则您仅使用41位。 对于64位,41x2仍然太大。 尽管我不能保证它的速度,但是有一个128位的比较和交换。 我总是尝试使用64位之一。
也许您只需要多达20亿个节点。 因此,您可以做的是预分配列表所来自的节点池。 您可以通过使用原子操作获取下一个空闲池索引来创建节点。 然后,它们可能不是节点指针的下一个和上一个指针,而是节点池中的31位索引,您还有2位用于删除标志。 假设您不需要20亿个节点,那么您将剩下更多的位。 唯一的缺点是您必须知道启动时需要多少个节点,尽管您也可以重新分配节点。
我所做的是使用虚拟内存功能来保留GB的地址空间,然后根据需要将物理内存映射到该内存中,以扩展我的池而不必重新分配。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.