【问题标题】:C code of lock-free queue无锁队列的C代码
【发布时间】:2011-08-30 20:09:04
【问题描述】:

如何在C 中实现这个无锁队列伪代码?

ENQUEUE(x)
    q ← new record
    q^.value ← x
    q^.next ← NULL
    repeat
        p ← tail
        succ ← COMPARE&SWAP(p^.next, NULL, q)
        if succ ≠ TRUE
            COMPARE&SWAP(tail, p, p^.next)
    until succ = TRUE
    COMPARE&SWAP(tail,p,q)
end

DEQUEUE()
    repeat
        p ← head
        if p^.next = NULL
            error queue empty
    until COMPARE&SWAP(head, p, p^.next)
    return p^.next^.value
end

如何使用Built-in functions for atomic memory access

__sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)

我现在有

typedef struct queueelem {
    queuedata_t data;
    struct queueelem *next;
} queueelem_t;

typedef struct queue {
    int capacity;
    int size;
    queueelem_t *head;
    queueelem_t *tail;
} queue_t;

queue_t *
queue_init(int capacity)
{
    queue_t *q = (queue_t *) malloc(sizeof(queue_t));
    q->head = q->tail = NULL;
    q->size = 0;
    q->capacity = capacity;
    return q;
}

【问题讨论】:

  • 使用全局变量的唯一“方便”是它可以让你变得懒惰并避免一些打字。既然你正在做并发编程,你应该为你甚至问过这个问题而感到羞耻。 :-)
  • 这与您提出的最后一个问题有何不同(例如,不是重复)?
  • 请注意,以_t 结尾的名称在某些标准(POSIX,不确定实际标准)中保留,不应在您的代码中使用。您应该使用不同的命名约定。
  • Lock-free queue的可能重复
  • 一些生活规则。 1) 始终从列表的 HEAD 中添加/删除。快点!更简单!单个 CAS 将从列表中添加/删除项目,并且您消除了大量的竞争条件。如果你必须做不止一个 CAS,你将不得不处理比赛条件。使用单个 CAS 无法更改尾部项目的下一个 ptr,然后同时更改头部或尾部。改变列表的头指针是! 2) 仅当您移除物品并将其放回时才担心 ABA。如果您只将新的 ptrs 添加到列表中,您将不会拥有 ABA。

标签: c queue lock-free


【解决方案1】:

https://www.liblfds.org/

公共领域,无许可证,C 语言无锁算法的可移植实现。

为 Windows 和 Linux 开箱即用。

在 Linux 上使用 GCC,因此使用内部函数(嗯,除了 128 位 CAS,没有内部函数 - 为此使用内联汇编)。

包含 M&S 队列。看看源代码,看看它是如何完成的。

【讨论】:

  • 网站似乎已关闭
【解决方案2】:

如果您的目标是生产代码,请不要这样做;使用锁。

your previous question 中,您已经获得了足够的信息来解释原因。由于the (in)famous ABA problem,在没有垃圾收集器的情况下,即使是简单的数据结构(例如队列和堆栈)的正确无锁实现也是棘手和复杂的。不幸的是,一些研究论文无论出于何种原因都没有考虑到 ABA。您的伪代码似乎取自其中一篇论文。如果你把它翻译成 C 并为节点使用堆分配的内存,如果在实际代码中使用它会导致不确定的错误。

如果您做这些事情是为了获得经验,那么不要指望 SO 研究员会为您解决它。您必须阅读所有引用的材料等等,确保您真正了解无锁算法(如 ABA)的所有细微差别,研究旨在解决该问题的各种技术,研究现有的无锁实现等。

最后,将给定的伪代码翻译成 C 语言的指导很少:

q^.value ← x 表示q_elem->data = x;
repeat ... until COMPARE&SWAP(head, p, p^.next) 相当于do {...} while (!__sync_bool_compare_and_swap(q_obj->head, q_elem, q_elem->next);

其中q_objqueue_t 类型的实例(即队列),q_elemqueueelem_t 类型的实例(即队列节点)。

【讨论】:

  • 如果我在生产中需要更高的性能怎么办?恕我直言,对于几乎所有比赛条件来说,锁都是矫枉过正的。
  • 如果我能得到一个无锁队列,我更愿意得到它,尤其是在生产代码中。
【解决方案3】:

虽然不完全是 C,但请查看 proposed Boost.Lockfree 库。内部结构很容易理解,可以移植到 C,或者相反,您可以将 Boost.Lockfree 包装在 C API 中并使用它。

同样,Boostcon 2010 有很多关于无锁编程和 STM 的讨论,如果您对此主题感兴趣,值得一看。我找不到视频的链接,但英特尔、IBM 和 AMD 的演讲值得一看,因为他们正在处理 CPU 级别的 STM。

【讨论】:

    【解决方案4】:

    听起来你想要的东西叫做 MCS 队列锁(虽然名字很假,但它确实是无锁的,只是不是无等待的),这里有一些很好的伪代码:http://www.cs.rochester.edu/research/synchronization/pseudocode/ss.html#mcs

    【讨论】:

    • 它的名字不是骗人的,因为它实现了一个锁,即互斥算法。
    【解决方案5】:

    我用 C 写了一个最小化无锁队列实现。

    lfq.

    它支持多生产者,多消费者。

    【讨论】:

    • 您不需要 asm volatile( "lfence" ) 来设置负载屏障。在 x86 上,asm("" ::: "memory") 就足够了。您似乎将 C++ 的编译时内存模型与 x86 的运行时内存模型混淆了。见When should I use _mm_sfence _mm_lfence and _mm_mfence。你真的不需要任何手工制作的 asm 东西或 volatile,只需使用 C11 stdatomic
    • 你的作者和读者也为了ctx->count相互竞争。在lfq_enqueue 中,p->next = insert_node; 不应该在 CAS 循环内吗?您当前在该函数中的 CAS 循环可以优化为无条件的ctx->tail = insert_node,因此它可能没有按照您的意图进行。 mb() 也是不必要的;那时您只修改了私有对象。
    • 无论如何,编写自己的无锁队列是学习东西的好方法,但你的看起来还没有准备好供其他人使用。 (通常您会使用固定大小的循环缓冲区来避免在每个队列操作上分配/释放。在某些实现中,有一个全局空闲池,因此 calloc/free 相互竞争以访问相同的数据结构! )
    • 如何使用ctx->tail = insert_node来防止ABA问题?你能告诉我任何示例代码吗?
    • mb() 是第 120 行吗?如果你注释掉第 120 行,在你将 insert_node 分配到 ctx->tail 之前,作者会得到 insert_node。如果我注释掉第 120 行,我无法通过 1 小时的测试。我很高兴有人可以帮助我提高对无锁结构的了解。但我无法在不崩溃的情况下移除这些内存屏障。
    猜你喜欢
    • 1970-01-01
    • 2013-04-22
    • 2017-04-21
    • 2011-08-30
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 2014-12-25
    • 1970-01-01
    相关资源
    最近更新 更多