【问题标题】:How to design multithreaded application如何设计多线程应用程序
【发布时间】:2010-07-29 21:31:37
【问题描述】:

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

- 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 通过条件变量和互斥体同步)。

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

这是一个相当复杂的应用程序,设计看起来“合理”。另一方面,我不确定这样的设计,每个模块的线程,是“最好的”吗?特别是,恐怕这样的设计会“鼓励”很多上下文切换。

你怎么看?

有没有什么好的指南或开源项目可以学习如何“正确”设计线程应用程序?

【问题讨论】:

  • 这真的取决于应用程序做什么,以及要求是什么。在某些应用程序中,感知复杂性对于满足模块化和关注点分离的要求是必要的。

标签: c++ multithreading


【解决方案1】:

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

这种设计效率低下,因为很少有任务分解会产生与 CPU 数量一样多的任务。

更合理的设计是将任务分解为“作业”,然后使用线程池机制来分派这些作业。 相对于每模块线程方法的优势:

  • 线程池利用所有内核。如果您有模块

  • 线程池通过保持活动线程和内核之间的奇偶性来最大限度地减少争用和资源。对于每个模块的线程,如果模块 > 内核会导致不必要的额外上下文切换,并且(在某些平台上)每个线程会耗尽其他有限的每个进程资源(如虚拟内存)。

  • 线程池允许一个“模块”一次执行多个作业。 thread-per-module 意味着最繁忙的模块仍然只有一个核心。

【讨论】:

    【解决方案2】:

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

    此时,我的一般经验法则是我必须证明每个新线程的存在是合理的。例如,如果我使用的网络层同时提供同步和异步 API,我真的可以证明让网络代码在新线程中使用同步调用而不是仅在主线程中使用异步调用吗?在您的情况下,有多少模块出于特定原因实际上需要自己的线程。有没有可以从主线程依次调用的方法?

    如果某些线程没有充分的理由存在,那么您可能只需将该模块放在主线程中即可为自己节省一些麻烦和复杂性。

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

    我不知道有什么特别的“正确”方法可以做到这一点。很多事情真的归结为你的应用程序实际应该做什么的细节。

    【讨论】:

    • 如果我能给每个段落 U +1 ;)
    【解决方案3】:

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

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

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

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

    祝你好运!听起来像是一个复杂的系统,会带来很多小时的有趣开发!

    【讨论】:

      【解决方案4】:

      适用于什么平台?

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

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

      最终,仅从这个简短的描述很难判断您的设计是否合理。就我个人而言,我倾向于使用 IO 完成驱动的线程模型,而不是逻辑模块驱动的模型,但这只是我个人的想法。

      【讨论】:

        【解决方案5】:

        只是添加到其他答案,让我们推理您设计中的每个线程:

        • 网络模块

        接受。

        • 解析器模块 + 2个应用模块

        您确定这三个线程不能合并为一个主数据处理线程吗?如果是这种情况,您就可以像其他人建议的那样受益于线程池,由 N 个线程执行此处理。

        • 定时器模块

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

        • 计数器模块

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

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

        • 还有更多...

        希望不会!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-05-27
          • 1970-01-01
          • 1970-01-01
          • 2011-05-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-25
          相关资源
          最近更新 更多