boost::lockfree::spsc_queue 忙等待策略。有阻塞弹出吗?

Posted

技术标签:

【中文标题】boost::lockfree::spsc_queue 忙等待策略。有阻塞弹出吗?【英文标题】:boost::lockfree::spsc_queue busy wait strategy. Is there a blocking pop? 【发布时间】:2014-04-24 13:23:43 【问题描述】:

所以我使用boost::lockfree::spec_queue 通过两个 boost_threads 进行通信,该线程在我的应用程序中运行两个对象的函子。

除了spec_queue::pop() 方法是非阻塞的,一切都很好。即使队列中没有任何内容,它也会返回 True 或 False。但是,我的队列似乎总是返回 True(问题 #1)。我认为这是因为我预先分配了队列。

typedef boost::lockfree::spsc_queue<q_pl, boost::lockfree::capacity<100000> > spsc_queue;

这意味着要有效地使用队列,我必须忙于等待使用 100% cpu 不断弹出队列。我宁愿不要睡任意时间。我在java中使用了其他队列,这些队列会阻塞直到对象可用。这可以用 std:: 或 boost:: 数据结构来完成吗?

【问题讨论】:

你能指出 Java 中的哪些并发队列有阻塞弹出吗?很有可能(a)它不是无锁的(b)它有可以精确指定你可怕的“任意时间量”的可调参数 是的 - 只需使用 BlockingQueue 类。你想要你的蛋糕也吃吗? 【参考方案1】:

根据定义,无锁队列没有阻塞操作。

您将如何在数据结构上进行同步?由于显而易见的原因,没有内部锁,因为这意味着所有客户端都需要在其上同步,使其成为您的祖父锁定并发队列。

确实,您必须自己设计一个等待函数。你如何做到这一点取决于你的用例,这可能是库不提供的原因(免责声明:我没有检查过,也没有声称知道完整的文档)。

那么你能做什么:

正如您已经描述的,您可以在一个紧密的循环中旋转。显然,如果您知道等待条件(队列非空)总是会很快得到满足,您就会这样做。

或者,以特定频率轮询队列(同时进行微睡眠)。调度一个好的频率是一门艺术:对于某些应用程序,100ms 是最佳的,对于其他应用程序,潜在的 100ms 等待会破坏吞吐量。因此,并衡量您的性能指标(如果您的应用程序要部署在数据中心的多个核心上,请不要忘记功耗:))。

最后,您可以得出一个混合解决方案,旋转固定数量的迭代,如果没有任何结果,则诉诸(增加)间隔轮询。这将很好地支持突发性高负载的服务器应用程序。

【讨论】:

谢谢,我想我要问的是在 boost 中是否有阻塞队列。我现在就让它忙着等待。我可以稍后再回来。 有 boost.org/doc/libs/1_36_0/boost/interprocess/ipc/… 但没有更一般的 AFAICT。尽管仅在互斥锁下使用队列+条件变量似乎很简单。添加一个计数器,你就有了 MPMC 场景的信号量的逻辑等价物 “互斥锁下的条件变量”你能分享一下这个例子吗? @sehe 谢谢,如果你有代码将boost::lockfree::spsc_queue 与“互斥锁下的条件变量”包围起来以“阻止弹出”,那就太好了 @javapowered 是的。导致最佳延迟的IFF。如果负载持续高到足以使 CPU 内核饱和(例如,>60%),您可以选择支付额外的旋转功耗以挤出微小的额外响应。但是,大多数服务器应用程序都受到网络延迟的支配,只有当您预计基本上一直处于饱和状态时,无锁队列才有意义。【参考方案2】:

使用一个信号量让生产者在队列满时休眠,另一个信号量让消费者在队列空时休眠。 当队列既不满也不空时,sem_post 和 sem_wait 操作是非阻塞的(在较新的内核中)

#include <semaphore.h>

template<typename lock_free_container>
class blocking_lock_free

public:
    lock_free_queue_semaphore(size_t n) : container(n)
    
        sem_init(&pop_semaphore, 0, 0);
        sem_init(&push_semaphore, 0, n);
    

    ~lock_free_queue_semaphore()
    
        sem_destroy(&pop_semaphore);
        sem_destroy(&push_semaphore);
    

    bool push(const lock_free_container::value_type& v)
    
        sem_wait(&push_semaphore);
        bool ret = container::bounded_push(v);
        ASSERT(ret);
        if (ret)
            sem_post(&pop_semaphore);
        else
            sem_post(&push_semaphore); // shouldn't happen
        return ret;
    

    bool pop(lock_free_container::value_type& v)
    
        sem_wait(&pop_semaphore);
        bool ret = container::pop(v);
        ASSERT(ret);
        if (ret)
            sem_post(&push_semaphore);
        else
            sem_post(&pop_semaphore); // shouldn't happen
        return ret;
    
private:
    lock_free_container container;
    sem_t pop_semaphore;
    sem_t push_semaphore;
;

【讨论】:

转向内核来解决争用会破坏无锁队列的意义 我们只在队列为空或满时才进入内核,以避免繁忙的等待。这是两全其美的。 在低延迟应用程序中这是不可接受的,忙循环是唯一的选择。 ithare.com/infographics-operation-costs-in-cpu-clock-cycles - 原子是 15-30 个 cpu 周期,内核调用是 1000-1500。如果不需要能够比后者更快地做出反应,那么在内部结合两个世界的基于 futex 的队列可能是一个更容易的选择。 问题是lockfree中的阻塞弹出,只有当队列满/空时才会阻塞,这是唯一一次内核调用。如果你想在阻塞之前旋转一点,你可以添加一个 sem_trywait 循环

以上是关于boost::lockfree::spsc_queue 忙等待策略。有阻塞弹出吗?的主要内容,如果未能解决你的问题,请参考以下文章