单一制作人,多个消费者,有一些不寻常的曲折

时间:2017-07-08 17:08:56

标签: multithreading event-handling producer-consumer

我有一个(Posix)服务器,它充当许多客户端到另一个上游服务器的代理。消息通常从上游服务器向下流动,然后匹配,并推送到对该流量感兴趣的客户端的某个子集(维护来自上游服务器的FIFO顺序)。目前,这个代理服务器是单线程使用事件循环(例如 - 选择,epoll等),但现在我想使它成为多线程,以便代理可以更充分地利用整个机器并实现更高吞吐量。

我的高级设计是拥有一个N个工作线程的池(其中N是机器上核心数的一小部分),每个pthread都运行自己的事件循环。每个客户端连接将被分配给特定的工作线程,该线程随后将负责在该客户端连接的持续时间内满足该客户端的所有I / O +超时需求。我还打算让一个专用线程从上游服务器中提取消息。一旦读入消息,其内容可以被认为是不变/不变的,直到不再需要和回收它为止。工作人员永远不会改变消息内容 - 他们只是根据需要将它们传递给客户。

我的第一个问题是:客户兴趣的匹配是否最好由生产者线程或工作线程完成?

在前一种方法中,对于每个工作者线程,生产者可以检查工作者客户的兴趣(例如,组成员资格)。如果消息与任何客户端匹配,则它可以将消息推送到该工作者的专用队列。这种方法需要生产者和每个工人之间的某种同步,关于他们的客户很少改变利益。

在后一种方法中,生产者只是将每条消息推送到所有工作线程共享的某种队列上。然后,每个工作线程检查消息的所有,以便与其客户进行匹配。兴趣和处理匹配的每条消息。这是对通常的SPMC问题的一种扭曲,通常假设消费者单方面为自己采取元素,而不是所有消费者需要对每个元素进行一些处理。这种方法在多个线程中分配匹配工作,这似乎是可取的,但我担心可能会导致线程之间发生更多争用,具体取决于我们实现同步的方式。

在这两种方法中,当任何工作线程不再需要消息时,则需要回收它。因此,需要进行一些跟踪以了解何时没有工作线程需要消息。

我的第二个问题是:什么是跟踪任何工作线程是否仍然需要消息的好方法?

执行此操作的一种简单方法是为每条消息分配一个计数,指出在首次生成消息时仍需要处理消息的工作线程数。然后,当每个工作人员完成处理消息时,它将以线程安全的方式减少计数,如果/当计数变为零时我们就知道它可以被回收。

另一种方法是在消息进入时为消息分配64b序列号,然后每个线程都可以跟踪并记录它们以某种方式处理的最高序列号。然后我们可以以某种方式回收序列号小于或等于所有工作线程中最小处理序列号的所有消息。

后一种方法似乎可以更轻松地允许延迟回收过程,同时需要更少的跨线程同步。也就是说,你可以进行清理"只运行周期的线程,并计算工作线程的最小值,并且需要更少的线程间同步。例如,如果我们假设64b整数的读取和写入是原子的,并且工作者的完全处理的序列号总是单调递增,那么"清理"线程可以只是定期读取工人'完全处理的计数(可能带有一些内存障碍)并计算最小值。

第三个问题:工人们认识到他们在队列中有新工作要做的最佳方法是什么?

每个工作线程将管理自己的客户端文件描述符和超时事件循环。对于每个工作线程来说,最好只有自己的管道,生产者可以将信号数据写入其中以便将它们付诸行动吗?或者他们应该定期检查他们的队列是否有新工作?有更好的方法吗?

最后一个问题:我应该为生产者和消费者之间的队列使用什么样的数据结构和同步?

我知道无锁数据结构,但我不能很好地了解他们在我的情况下是否更受欢迎,或者我是否应该只使用简单的互斥锁影响队列的操作。此外,在共享队列方法中,我并不完全确定工作线程应如何跟踪"其中"它正在处理队列。

任何见解都将不胜感激!谢谢!

1 个答案:

答案 0 :(得分:1)

根据您的问题描述,无论如何都需要为每个客户端对每个消息进行客户兴趣的匹配,因此匹配中的工作与它出现的任何类型的线程相同。这表明匹配应该在客户端线程以提高并发性。如果“生产者”线程确保将消息刷新到主内存(技术上,“将内存与其他线程同步”),然后其他线程知道其可用性,则同步开销不应成为主要问题,如客户端线程所有人都可以同时从主存储器中读取信息而不会相互同步。客户端线程将无法修改消息,但它们不需要。

通过跟踪每个线程的当前消息号而不是通过具有消息特定计数器,消息回收可能更好,因为消息特定计数器提出了并发瓶颈。

我认为您不需要正式的排队机制。 “生产者”线程可以简单地保持一个更新的volatile变量,其中包含已刷新到主内存的最新消息的数量,客户端线程可以在它们可以自由工作时检查变量,如果没有工作就会睡觉可用。您可以在线程管理方面变得更加复杂,但额外的效率提升可能会很小。

我认为您不需要复杂的数据结构。您需要volatile变量来获取可用于处理的最新消息的数量以及每个客户端线程已处理的最新消息的数量。您需要将消息本身刷新到主内存。您需要某种方法从消息编号中查找主内存中的消息,可能使用指针的循环缓冲区,或者如果消息长度相同则使用消息。关于要在线程之间传递的数据,你真的不需要太多其他内容。