对于 LRU 缓存,我需要一种用于无锁队列的算法,类似于简单、快速和实用的非阻塞和阻塞并发队列算法一文中描述的算法
但是为了维护一个 LRU 队列,我还需要删除队列中的节点(尾部节点除外)。
我的想法是仅使用 CAS 操作将节点标记为已删除,然后使用单个清理线程,该线程稍后将从队列中删除已删除的节点。
我发现创建无锁算法比我最初预期的要复杂。所以,我的问题是:
有没有这样的算法可用?
这是我目前使用的结构:
一般的
- 一个节点具有以下结构: struct { e *entry, next pointer, prev pointer, state uint32}
- 为了避免为每个新节点分配多个内存,分配了一个节点数组。
- 指向节点的指针由数组索引值和更新计数器组成,复用到单个 uint64
- 节点状态由一个高位组成,告诉该节点是否被删除。其余位用作更新计数器
入队
- 辅助队列保存未使用节点的列表,“新”节点通过从辅助队列中的出队获取,然后设置为默认值
- node.prev 在新节点入队之前设置为当前尾部
出队
- head.next.prev 在 head dequeue 之前被 CAS 化为 NIL 值。如果 head.next.prev 设置为 CLEANUP(由清理线程处理),则不允许出队,线程将让出 CPU 并重新开始。
- 在具有未删除状态的节点成功出列时,该状态将被 CAS 删除,并且该节点将返回到辅助队列。在失败(或状态已设置为已删除)时,出队的 node.prev 将从 NIL 更改为 CLEANUP,向清理线程发出该节点已出队的信号。然后出队将重新开始,直到未删除的节点成功出队并通过 CAS 删除。
删除
- 为了标记队列中的删除,状态被 CAS'ed 删除并在成功时传递给清理队列。失败时什么也不做,但函数返回。
清理线程
- 如果 node.prev 是 CLEANUP,则 Dequeue 已将其从队列中删除。节点被传回辅助队列。
- 如果 node.prev 为 NIL,则该节点即将成为头,是头,或即将出队。如果 node == head,清理线程尝试执行出队(状态更改为已删除)。失败时,清理过程将从头开始。
- 如果 node 被设置为另一个节点,node.prev 被 CAS'ed 为 CLEANUP。如果 head.next == 节点,这可以防止任何出队。成功后,使用双链表进行队列内删除。失败时,清理过程将从头开始。
Node.prev 用于告诉:
- 队列中哪个节点在前
- 节点即将成为头/是头
- 节点正在被清理线程处理
- 节点出队