【问题标题】:How do I build a lockless queue?如何构建无锁队列?
【发布时间】:2010-12-16 00:40:21
【问题描述】:

我今天花了很多时间研究无锁队列。我有多个生产者,多个消费者的情况。为了测试,我在 Win32 下使用 Interlocked SList 实现了一个系统,它使我的基于重线程任务的代码的性能翻了一番。但不幸的是,我希望支持多个平台。在多个平台上互锁本身不是问题,我可以放心地假设我可以毫无问题地互锁。然而,实际的实现让我迷失了。

最大的问题似乎是您需要保证列表推送/弹出将仅使用一个互锁调用。否则,您将留出空间让另一个线程咬入并将事情搞砸。我不确定微软的实现是如何在幕后工作的,我很想知道更多。

谁能告诉我有用的信息(平台和语言无关紧要)?

此外,我很想知道是否可以实现无锁向量。这对我有很大的用处:) 干杯!

编辑:阅读herb 的DDJ 文章后,我可以看到一个减少的锁定队列,这与我已经拥有的非常相似。但是我注意到最后有一些论文可以使用双重比较和交换(DCAS)操作进行真正的无锁队列。有没有人使用 cmpxchg8b (或 cmpxchg16b )实现队列?

此时我只是在沉思(尚未阅读论文),但您可以使用此系统同时更新头指针和尾指针,从而避免另一个线程在两个原子操作之间跳转的任何问题。但是,您仍然需要获取下一个头指针以针对尾指针进行测试,以查看您是否刚刚修改了尾。当另一个线程准备自己这样做时,如何避免另一个线程更改此信息?这究竟是如何以无锁方式实现的?还是我最好阅读研究论文的不可破译性? ;)

【问题讨论】:

  • 很遗憾你没有回答这个问题 :) 我没有找到那个队列,此外,我没有找到 Herb Sutter 的多制作人/消费者 DDJ 文章 :) 谢谢!

标签: language-agnostic queue atomic lockless


【解决方案1】:

These 伙计们,也许你可以在那里找到一些灵感。其他有趣的文件是 yqueue.hpp 和 atomic_ptr.hpp

【讨论】:

    【解决方案2】:

    我认为关于这个话题有一些有趣的讨论here,尤其是this thread.

    【讨论】:

      【解决方案3】:

      您可能可以轻松地实现一个大小有限的队列...我最近在考虑它并提出了这个设计,但您可能会发现许多其他有趣的想法:(警告:它可能有一些问题! )

      • 队列是一个指向项的指针数组
      • 您必须管理 2 个指针(头、尾),它们在队列上的工作方式与循环缓冲区相同
      • 如果head == tail,则没有项目
      • 如果你想enqueue(ptr), Interlocked-Swap tail with NULL (prev_tail 是交换的值)
        • 如果prev_tail == NULL,再试一次
        • 如果prev_tail + 1(带环绕)== head,则您的队列已满
        • 否则将您的ptr 放入*prev_tail 并将prev_tail+1 分配给tail(注意缓冲区环绕)
      • dequeue() 复制 tmp_head=head 并检查tmp_head == tail
        • 如果为真,则返回,因为队列为空
        • 如果它是假的
          • *tmp_head另存为ptr
          • 做一个CAS:比较headtmp_head交换headhead+1
          • 如果 CAS 失败 -- 重新启动整个函数
          • 如果成功——返回ptr

      head 和 tail CAS 操作都可以等待,但是如果队列没有被争用,你应该第一次成功,没有不必要的锁。

      无限大小的队列“有点”困难;)但是您应该能够创建一个足够大的队列来满足大多数需求。

      【讨论】:

      • 你将如何检查head == tail 是否在没有锁的情况下出队?
      • 好的 - 我没有正确描述该步骤。您必须循环到开头,而不是围绕 CAS 本身。 CAS 将检测头部是否移动。
      • 听起来不错,我同意扩展的解释。只是检查:)。
      • 基本上你所描述的是队列上的自旋锁,不是吗?为什么不直接使用基于自旋锁的临界区?性能会一样,不是吗?至于说如果队列没有争用一切都会好的......如果它没有争用那么你也可以使用关键部分;)
      • 几乎 - 使用自旋锁,您实际上必须锁定在不同的位置,这使得整个事情变得更长一些操作。我不知道有什么好处,但是如果您足够关心寻找无锁解决方案,那么很难找到比一个作业 + 一个 cas 更快的版本。
      【解决方案4】:

      viraptor 解决方案是锁定,我知道没有多个生产者/多个消费者无锁队列算法。

      【讨论】:

      • 我认为它是无锁的——它不包含互斥锁。它确实需要同步,但任何线程安全的东西也是如此。如果没有重试的可能性(操作本身或重试互斥锁),您将无法使任何线程安全。
      • 抱歉,我没有仔细检查您的解决方案是典型的单一生产者/消费者 Cas 队列,我认为它是自旋锁。然而,在做 MULIPLE CONSUMER 和 MULTIPLE Producer 时,没有关于 CAS 解决方案的科学论文都失败了。
      【解决方案5】:

      您可能想看看 Herb Sutters 对低锁队列的实现。

      http://www.drdobbs.com/hpc-high-performance-computing/211601363

      它确实使用了 c++0x 原子,但它(应该)很容易通过您的特定架构原子操作(使用 GNU 的 __sync_*、solaris 上的 atomic_* 等)来实现。

      【讨论】:

      • “我们仍在编写自旋锁;我们只是手动编写它们。这意味着它不是纯粹的‘无锁’或非阻塞算法。”它不是无锁的,他们只是滚动了自己的自旋锁。
      猜你喜欢
      • 2017-04-21
      • 2011-08-30
      • 1970-01-01
      • 1970-01-01
      • 2013-04-22
      • 2012-12-18
      • 2011-10-10
      • 2011-08-30
      • 1970-01-01
      相关资源
      最近更新 更多