无锁双链表的原子操作
Atomic operations for lock-free doubly linked list
我正在根据这些论文编写一个无锁的双向链表:
"基于引用计数的高效可靠的无锁内存回收"Anders Gidenstam,IEEE成员,Marina Papatriantafilou,H° akan Sundell和Philippas Tsigas
"无锁双链表和双向链表"哈坎·桑德尔,菲利帕斯·齐加斯
对于这个问题,我们可以把第一篇论文放在一边。
在本文中,他们使用一种智能方式在单词中存储删除标志和指针。(更多信息在这里)
本文中本节的伪代码:
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
比较和交换函数中的变量并不重要,因为它是线程本地的)
现在我的问题:我如何编写比较和交换函数来比较和交换三个原子变量,或者我在哪里犯了错误?
(请原谅我的长篇提问)
编辑:
内存管理器论文中也有类似的问题:
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上,我也没有听说过任何其他机器架构有这样的东西)。
如果您依赖于存储在(至少)与偶数字节地址对齐的地址上的数据,则在删除元素时,可能会使用按位 OR 设置最低位。过去,人们一直使用地址的上半部分来存储额外的数据,但至少在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
标志。
您应该能够通过使用 a 形式的 cmpxchg16b
(在 x86 上)对双链表执行此操作。在Windows系统中,这将是_InterlockedCompareExchange128
。在gcc(对于Unix类型的操作系统,如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 位。 41x2 对于 64 位来说仍然太大。 有一个 128 位的比较和交换,尽管我不能保证它的速度。 我总是尝试使用 64 位的。
也许您只需要多达 20 亿个节点。 因此,您可以做的是预先分配列表从中提取的节点池。 当然,您可以通过使用原子操作获取下一个可用池索引来创建节点。 然后,它们可能是节点池中的 31 位索引,而不是 next 和 prev 作为指针,并且您还剩下 2 位用于删除标志。 假设您不需要 20 亿个节点,那么您还剩下更多位。 唯一的缺点是你必须知道在启动时需要多少个节点,尽管如果你也有的话,你可以重新分配节点。
我所做的是使用虚拟内存函数来保留 GB 的地址空间,然后将物理 ram 映射到该空间,因为我需要它来扩展我的池,而无需重新分配。
- 反向给定链表中的K节点
- 如果没有malloc,链表实现将失败
- 文本文件中的单词链表
- 努力将整数转换为链表。不知道我在这里做错了什么
- 链表,反向函数,数据结构
- 使用 XOR 操作仅使用 2 个指针反转链表
- 在 "CodePad" 中执行链表操作时转储的核心(这是一个在线C++编译器)
- 链表回推操作中需要'back pointer'
- 操作链表的函数出现逻辑错误
- 链表操作C++
- 为什么单向链表的 Next() 操作应该用关键部分来保护
- 无锁双链表的原子操作
- 链表操作,检索数据 c++ 的问题
- 尝试仅通过操作指针对链表进行排序
- 使用链表进行堆栈弹出操作
- 使用c++在链表中搜索操作
- 链表操作的段错误
- 比较交换原子操作vs加载链接/存储条件操作
- 仅通过操作指针交换链表中的相邻节点
- c++链表操作