什么时候使用破坏者模式和当工作窃取本地存储?

Dav*_*Far 11 concurrency concurrent-programming disruptor-pattern work-stealing

以下是否正确?

  • 破坏者模式具有更好的并行性能和可扩展性,如果每个条目有多种方式(IO操作或注释)进行处理,因为可以使用多个消费者不争进行并行化.
  • 相反,工作窃取(即在本地存储条目和从其他线程窃取条目)具有更好的并行性能和可伸缩性,如果每个条目必须仅以单一方式处理,因为在干扰模式中将条目不相交地分配到多个线程会导致争用.

(当涉及多个生产者(即CAS操作)时,破坏者模式是否仍然比其他无锁多生产者多消费者队列(例如来自提升)快得多?)


我的情况详细:

处理条目可以产生几个新条目,这些条目也必须最终处理.性能具有最高优先级,以FIFO顺序处理的条目具有第二优先级.

在当前实现中,每个线程使用本地FIFO,在其中添加新条目.空闲线程从其他线程的本地FIFO中窃取工作.线程处理之间的依赖关系使用无锁,机械同情的哈希表(写入时的CAS,具有桶粒度)来解决.这导致相当低的争用,但FIFO顺序有时会被破坏.

使用干扰模式可以保证FIFO顺序.但是不会将条目分配到线程上导致更高的争用(例如,读取游标上的CAS),而不是工作窃取的本地FIFO(每个线程的吞吐量大致相同)?


我发现的参考文献

关于破坏者的标准技术论文(第5章+6)中的性能测试不包括不相交的工作分布.

https://groups.google.com/forum/?fromgroups=#!topic/lmax-disruptor/tt3wQthBYd0是我在disruptor +偷窃工作中发现的唯一参考.它声明如果存在任何共享状态,每个线程的队列会显着减慢,但不会详细说明或解释原因.我怀疑这句话适用于我的情况:

  • 使用无锁哈希表解析共享状态;
  • 必须在消费者之间不相交地分发条目;
  • 除了工作窃取之外,每个线程只在其本地队列中进行读写.

jas*_*onk 13

更新 - 提供最高性能的底线:你需要用破坏者和工作窃取的惯用语法编写,然后再进行基准测试.

对我来说,我认为区别主要在于消息与任务焦点之间的分离,因此也就是你想要思考问题的方式.尝试解决您的问题,如果它是以任务为中心,那么Disruptor是一个不错的选择.如果问题是以消息为中心,那么您可能更适合另一种技术,例如工作窃取.

  • 当您的实现以消息为中心时,使用工作窃取.每个线程都可以获取一条消息并运行完成.对于示例HTTP服务器 - 为每个入站http请求分配一个线程.该线程专注于处理请求开始完成 - 记录请求,检查安全控制,执行vhost查找,获取文件,发送响应和关闭连接

  • 当您的实施以任务为中心时,请使用disruptor.每个线程都可以在处理的特定阶段工作.替代示例:对于任务焦点,处理将分为几个阶段,因此您将拥有一个执行日志记录的线程,一个用于安全控件的线程,一个用于vhost查找的线程等; 每个线程都专注于其任务,并将请求传递给管道中的下一个线程.阶段可以并行化,但整体结构是专注于特定任务的线程,并在线程之间传递消息.

当然,您可以更改实施以更好地适应每种方法.

在您的情况下,如果您想使用Disruptor,我会以不同的方式构建问题.通常,您可以通过让单个线程拥有状态来​​消除共享状态,并通过该工作线程传递所有任务 - 查找SEDA以获取大量此类图表.这可以带来很多好处,但同样,这取决于您的实施.

更冗长一点:

  • Disruptor - 当需要严格的阶段排序时非常有用,当所有任务具有一致的长度时具有额外的好处,例如:外部系统没有阻塞,每个任务的处理量非常相似.在这种情况下,您可以假设所有线程将在系统中均匀工作,因此可以安排N个线程来处理每N条消息.我喜欢将Disruptor视为实现线程处理阶段的SEDA类系统的有效方式.你当然可以在一个阶段和一个多个并行单元的应用程序在每个阶段执行相同的工作,但这不是我认为的重点.这将完全避免共享状态的成本.
  • 工作窃取 - 当任务具有不同的持续时间并且消息处理的顺序不重要时使用此功能,因为这允许空闲且已经消耗其消息的线程从另一个任务队列继续进行.这种方式,例如,如果您有10个线程,1在IO上被阻止,其余的仍将完成其处理.

  • 重新关注消息v任务集中。HTTP Web服务器的示例:消息焦点-为每个入站的HTTP请求分配一个线程。该线程专注于处理从头到尾的请求-记录请求,检查安全控件,执行vhost查找,获取文件,发送响应以及关闭连接。或者,以任务为中心,处理将分为多个阶段,因此您将拥有一个进行日志记录的线程,一个用于安全控制的线程,一个用于vhost查找的线程等。每个线程专注于其任务,并将请求传递到管道中的下一个线程。 (2认同)