【问题标题】:Most efficient way of writing a single producer/single consumer queue编写单个生产者/单个消费者队列的最有效方式
【发布时间】:2012-07-06 12:12:39
【问题描述】:

编写生产者/消费者队列的最有效方法是什么,其中一个线程是生产者,另一个是消费者。在一篇论文中,作者说将一个元素插入他的队列需要一个原子操作,但他没有解释如何。

另外,他的队列是一个循环队列,如果队列为空,消费者等待,如果队列满,生产者等待。他怎么可能实现这样的队列。他所说的原子操作是指某种互斥体还是只是一个原子变量。注意他说,一个原子操作。

【问题讨论】:

    标签: c linux gcc x86-64


    【解决方案1】:

    如果您没有实现无锁队列的经验,唯一的答案可能是:最有效(和安全)的方法是使用锁,由 pthread(互斥锁或 cond var)提供。

    无锁算法通常(但不一定)会给你一点额外的性能,但如果你不知道自己在做什么,它们可能会出错。

    另一方面,Linux 下的 phtreads 实现尽可能避免锁定,并在需要时使用 futex(同样,futex 速度很快,但您应该知道自己在做什么)。
    这样的队列每秒通过十万个任务没有问题。这可能看起来很有限,但实际上 如果您需要每秒通过 1000 万个任务,那么您做错了。理想情况下,您可能每秒在队列上传递几十到一百个左右的任务(任务更少,但工作组更大)。你想例如创建 50 个任务,每个任务处理半兆字节的数据,而不是 2500 万个任务处理一个字节的数据。

    如果您仍然坚持尝试无锁实现(可能出于学术兴趣),您将需要一个原子比较交换操作(在 C 的 GCC 文档中查找“遗留 __sync 函数”,对于 C++,您会使用新的原子操作)。
    请务必阅读诸如 ABA 之类的细微细节,为此您通常需要某种指针操作(将引用计数存储在最低 3 位中)或具有显式引用计数的双字交换。

    或者,如果您做出一些假设,则可以仅使用原子添加或根本不使用任何原子操作来实现无锁队列(如果您只是出于好奇而感兴趣,请参阅“快进队列”)。然而,这些只有在假设成立的情况下才有效,而且它们更容易出错,所以最好远离它们。

    【讨论】:

    • 我没有使用队列来处理任务,而是保存将由生产者生成并由消费者读取的数据。而且数据很多。该程序很容易在一秒钟内拥有数百万个元素。你能猜出作者会用什么吗?我推测他使用了某种无锁算法。
    • 一个典型的带有 CAS/DCAS 的无锁队列(例如__sync_val_compare_and_swap)可以在我的系统上处理大约 700-800k msg/s。对于“可能很容易达到数百万”,您可能需要一个快进队列,因为原子操作还不够快。但是同样,如果您谈论“数百万”,那您就做错了。让生产者将 500-1000 个项目打包到一条消息中,让消费者一次处理 1000 个。真的。这是正确的做法。
    • 请注意,您不希望完美的并行性降低到一项。您希望将工作划分得足够细,以便可以将它们安排到多核/处理器机器(甚至多台机器,通过网络)上的不同内核。比必要的更精细的粒度无济于事,而且成本更高。例如,如果您有 1000 万个任务,并且您在一个批次中通过了 1000 个,那就是 10,000 个批次——这足以保证近乎完美的平衡。
    • 注:“任务”或“数据”是一样的,只是措辞一般。即使“处理”仅意味着“保留它,保存它”。关于平衡,如果您不想要并行性,那么您可以让生产者将所有内容写入一个大缓冲区(可能是兆字节)并不时地一次移交一个缓冲区 - 在这种情况下您需要的粒度更少。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-08
    • 1970-01-01
    • 2019-05-18
    • 2011-02-11
    • 1970-01-01
    相关资源
    最近更新 更多