如何设计多线程应用程序

时间:2010-07-29 21:31:38

标签: c++ multithreading

我有一个多线程应用程序。每个模块都在一个单独的线程中执行。 模块是:

- network module - used to receive/send data from network
- parser module - encode/decode network data to internal presentation
- 2 application module - perform some application logic on the above data one after other
- counter module - used to gather statistics from other modules
- timer module - used to schedule timers
- and much more ...

使用消息队列进行内部线程通信的所有线程(通过条件变量和互斥锁进行std :: deque同步)。

某些模块被其他模块使用(例如,所有模块都使用定时器和计数器),这对于从网络接收的每条消息都应该以非常高的速率处理。

这是非常复杂的应用程序,设计看起来“合理”。另一方面,我不确定这样的设计,每个模块的线程是“最好的”吗?特别是,我担心这样的设计“包含”了很多上下文切换。

您怎么看?

是否有任何好的指导方针或开源项目可以学习如何“正确”设计线程应用程序?

5 个答案:

答案 0 :(得分:22)

每个函数的线程设计只是天真的:他们假设通过将任务(按模块)分离到线程上,可以实现某种可伸缩性。

这种设计效率低下,因为很少有任务分解产生与CPU相同的任务。

更合理的设计是将任务分解为“工作” - 然后使用线程池机制来分配这些工作。 每个模块线程方法的优点:

  • 线程池利用了所有核心。如果你有模块<使用每个模块的线程核心处于空闲状态。

  • 线程池通过维护活动线程和核心之间的奇偶校验来最小化争用和资源。使用每个模块的线程,如果模块>核心你需要不必要的额外上下文切换和(在某些平台上)每个线程耗尽其他有限的每个进程资源(如虚拟内存)。

  • 线程池让“模块”一次执行多个作业。每个模块的线程意味着最繁忙的模块仍然只有一个核心。

答案 1 :(得分:7)

我不会称自己为多线程设计专家。但我至少使用线程足以遇到各种问题,试图将它们设计为一起工作(通信,锁定资源,等待线程结束等)。

此时,我的一般经验法则是我必须证明每个新线程的存在。例如,如果我正在使用的网络层同时提供同步和异步API,那么我真的可以证明网络代码在新线程中使用同步调用而不是仅使用主线程中的异步调用吗?在您的情况下,由于特定原因,有多少模块实际上需要自己的线程。是否有任何可以反过来从主线程轮流调用?

如果某些线程没有充分理由存在,那么可能只需将该模块放在主线程中就可以省去一些麻烦和复杂性。

当然,现在有充分理由把事情放在线程中。例如,进行可能长时间阻塞的同步调用,在执行长任务时保持GUI线程响应,或者能够利用多核系统上的大型任务的并行处理。

我不知道有任何特殊的“正确”方法。很多事情都归结为你的应用程序实际应该做的事情的细节。

答案 2 :(得分:3)

一个好的准则是将可能阻塞的操作(例如I / O)放在自己的线程中。您的网络模块是这里的明确候选人。让您的网络线程使用select(我在这里假设UNIX)来阻止输入。

异步事件在单独的线程中也很好。你的计时器模块在这里看起来很合适。

您可能希望将其他模块放在一个线程中,以降低应用程序的复杂性。但是,如果你有一个多处理器系统,你可能想要拆分它们。

有一个很好的策略来锁定资源和互斥锁处理以防止死锁。依赖图(使用白板!)可能有助于您的设计正确。

祝你好运!听起来像一个复杂的系统,将导致许多小时的乐趣发展!

答案 3 :(得分:1)

对于什么平台?

例如,Win32应用程序后端服务器的最佳模型(如同您的那样)是线程池和IO完成端口。这不仅仅是一些听到的说法和意见,这种说法背后有很多事实。 Windows性能团队的Rick Vicik发布了一系列文章,更详细地描述了为什么高端服务器需要遵循此模型,请参阅High Performance Windows Programs

还有其他因素可以发挥作用,例如网络模块必须处理的协议错误。请求 - 响应协议通常由每个请求一个线程的隐喻来处理,并且它们做得很好,但是高吞吐量的高规模协议在该模型中表现不佳,特别是因为盒装需求。

最终,您的设计是否合理,很难从这个简短的描述中分辨出来。我个人倾向于支持IO完成驱动的线程模型,而不是逻辑模块驱动的线程模型,但这只是我。

答案 4 :(得分:1)

只是为了添加其他答案,让我们推断你的dessign中的每一个线程:

  • 网络模块

接受。

  • 解析器模块 + 2应用程序模块

您确定这三个线程无法合并到一个主数据处理线程中吗?如果是这种情况,那么你就可以像其他人一样从线程池中受益,这个处理由N个线程完成。

  • 计时器模块

这在大多数平台上可能是合理的,因为您需要一个消息处理循环来调度计时器事件。此外,如果您需要一个可以作为场所的GUI。

  • 计数器模块

这是最让我烦恼的一个。我找不到为此设置单独线程的原因。根据您增加的数量,它将成为应用程序的一个很好的瓶颈。

我建议在每个线程中保留单独的计数器,并在需要时为它们进行轮询(消息队列)。

  • 等等......

希望不要!