我看到的工作队列的正常实现涉及互斥变量和条件变量。
消费者:
A) Acquires Lock
B) While Queue empty
Wait on Condition Variable (thus suspending thread and releasing lock)
C) Work object retrieved from queue
D) Lock is released
E) Do Work
F) GOTO A制片人:
A) Acquires Lock
B) Work is added to queue
C) condition variable is signaled (potentially releasing worker)
D) Lock is released我一直在浏览一些代码,我看到了一个使用POSIX管道的实现(我以前从未见过这种技术)。
消费者:
A) Do select on pipe (thus suspending thread while no work)
B) Get Job from pipe
C) Do Work
D) GOTO A制片人:
A) Write Job to pipe.由于生产者和使用者是同一个应用程序中的线程(因此它们共享相同的地址空间,因此它们之间的指针是有效的);作业被写入管道作为工作对象的地址(一个C++对象)。所以需要从管道中写/读的就是一个8字节的地址。
我的问题是:
我的好奇心被激发,因为管道技术不涉及任何可见的锁或信号(它可能隐藏在select中)。所以我想知道这样会不会更有效率?
编辑:
基于@Maxim Yegorushkin答复中的评论。
实际上,这个场景中的“生产者”并行地参与了大量来自许多源的高容量IO。所以我怀疑,原作者虽然很希望这个线程在任何情况下都不阻塞,但也不想在“生产者”线程中高成本工作。
发布于 2012-03-28 16:44:24
正如这里已经提到的,人们使用管道作为队列来避免阻塞非阻塞I/O线程中的条件变量(即在select/epoll上处理多个套接字和块的线程)。如果I/O线程在条件变量或互斥对象上阻塞,则不能再执行非阻塞I/O操作。
有人说,写入管道涉及系统调用,当线程间事件的数量很大时,可能会增加延迟。这只适用于简单的基于管道的队列实现。
高级实现使用无锁的作业/事件链接列表,并且只有当第一个作业被添加到列表中时,管道才会被写入以从阻塞的epoll调用中唤醒目标I/O线程(实际上使用管道作为边缘触发的通知机制,而不是传递指向作业/事件的指针)。因为唤醒线程需要几微秒时间,所以在此期间可能会有更多的作业/事件提交到线程的事件队列中,但是每个后续事件都不需要写入管道,直到稍后I/O线程醒来并消耗队列中的所有事件。此外,在更新的Linux内核中,可以使用更快的eventfd代替管道来唤醒I/O线程。
发布于 2012-03-28 16:23:47
我做过这件事。这是老派的,但很管用。
我这样做的原因是,我需要在作业上唤醒相同的线程来执行或读取来自另一个源的输入,因此涉及select()。
发布于 2012-03-28 16:17:46
我认为答案是,管道技术不能提供良好的性能,因为它涉及系统调用,这是相对昂贵的。但这确实意味着,所有那些棘手的锁定、睡眠和醒来都会由你来处理。
我自己也使用过,但管道只用于偶尔出现的非性能关键应用程序。
编辑:我想我还是提出标准的建议吧,因为没有人提出任何明确的权威意见。
标准建议是:尝试两者并对其进行基准测试。这是找出哪个表现更好的真正方法.
https://stackoverflow.com/questions/9911490
复制相似问题