2010-02-18 34 views
4

是在什么比使用阻塞调用pop()的差异,英特尔®线程构建模块并发队列:使用pop()方法在pop_if_present()

while(pop_if_present(...)) 

哪个应该优先于其他?为什么?

我正在寻找一个更深入的理解,就像在while(pop_if_present(...))的情况下进行轮询一样,让系统为你做这件事。这是一个相当普遍的主题。例如,boost::asio我可以做一个myIO.run()阻断或执行以下操作:

while(1) 
{ 
myIO.poll() 
} 

一个可能的解释是是调用while(pop_if_present(...))线程将保持很忙,所以这是不好的。但某人或某事不得不为异步事件进行轮询。为什么以及如何将其委托给操作系统或图书馆时更便宜?是否因为操作系统或图书馆对轮询的智能做了指数回退?

回答

3

英特尔TBB库是开源的,所以我接过来一看......

它看起来像pop_if_present()基本检查,如果队列为空,如果它立即返回。如果不是,它会尝试获取队列顶部的元素(这可能会失败,因为另一个线程可能已经出现并接受了它)。如果未命中,则在再次检查之前执行“atomic_backoff”暂停。 atomic_backoff只是简单地旋转了它被调用的前几次(每次增加两倍的旋转循环次数),但经过一定次数的暂停后,它只会屈服于操作系统调度程序,而不是旋转,因为它已经等待了一段时间,它可能做得很好。

对于普通的pop()函数,如果队列中没有任何东西会执行,则atomic_backoff会等待,直到队列中有东西出现为止。

注意,至少有2件有趣的事情(我反正)这一点:

  • pop()功能的东西在队列中露面执行自旋等待(最多点);它不会屈服于操作系统,除非它需要等待更多的短暂时间。所以,如你所料,没有太多的理由旋转自己打电话pop_if_present(),除非你有别的东西,你要去调用之间做pop_if_present()

  • pop()不会屈服于操作系统,它通过简单地放弃这样做这是时间片。它不会阻塞同步对象上的线程,当一个项目被放置在队列中时可以发送信号 - 它似乎进入睡眠/轮询周期来检查队列中是否有弹出的内容。这让我很吃惊。

借此分析一粒盐...我用此分析的来源可能是有点老了(它实际上是从concurrent_queue_v2.h和.cpp),因为最近concurrent_queue有不同的API - 没有pop()pop_if_present(),只是try_pop()功能在最新的class concurrent_queue接口。旧界面已被移动(可能有所变化)到concurrent_bounded_queue类。看起来,当构建库以使用OS同步对象而不是繁忙的等待和轮询时,可以配置较新的concurrent_queues。

2

随着while(pop_if_present(...))你正在队列上蛮力busy wait(也称为旋转)。当队列为空时,通过保持CPU繁忙来浪费周期,直到通过运行在不同CPU上的另一个线程将某个项目推入队列中,或者OS决定将您的CPU给予其他可能不相关的线程/进程。

你可以看到,这可能是坏的,如果你只有一个CPU - 生产者线程将无法推动,因而停止消费旋转,直到消费者的time quanta加上context switch的开销至少结束。显然是一个错误。

对于多个CPU,如果OS选择(或强制执行)生产者线程以在不同的CPU上运行,这可能会更好。这是spin-lock的基本思想 - 直接构建在特殊处理器指令(如compare-and-swapload-linked/store conditional)上的同步原语,并且在操作系统内部常用于在中断处理程序和内核的其余部分之间进行通信,并构建更高级别的构造,如semaphores

与封闭pop(),如果队列为空,你正进入睡眠等待,即要求OS把消费者线程进入不可调度状态,直到一个事件 - 推到队列 - 出现形成另一个线程。这里的关键是该处理器可用于其他(希望有用的)工作。 TBB实现实际上很努力地避免睡眠,因为它很昂贵(进入内核,重新计划等)。目标是优化队列不空的正常情况,并且可以快速检索项目。

的选择是非常简单的,但 - 总是睡等待,即不堵pop(),除非你必须忙等待(这是在实时系统,OS中断上下文,以及一些非常专业应用程序)。

希望这会有所帮助。

相关问题