每个连接的线程与Reactor模式(带有线程池)?

时间:2013-01-14 11:56:48

标签: c++ poco-libraries

我想写一个简单的多人游戏作为我的C ++学习项目的一部分。

所以我想,因为我在它,我想正确地做,而不是只是完成它

如果我理解正确:Apache使用每个连接的线程架构,而nginx使用事件循环,然后为传入连接专用工作[x]。我想nginx更明智,因为它支持更高的并发级别。正确?

我也遇到了这个clever analogy,但我不确定它是否适用于我的情况。这个比喻似乎也非常唯心主义。我很少看到我的计算机以100%的CPU运行(即使打开了一个不常见的Chrome标签,Photoshop也没有同时运行)

另外,我遇​​到了一个SO帖子(不知怎的,它从我的历史中消失了),用户询问他们应该使用多少个线程,其中一个答案是大约700个,甚至高达10,000个是完全可以接受的。线程。不过,这个问题与JVM有关。

所以,让我们估计一个虚拟的用户群,大约有5000个用户。哪种方法应该是“最并发”的方法?

  1. 在一个线程中运行所有内容的reactor模式。
  2. 带有线程池的反应器模式(大概,你建议线程池应该有多大?
  3. 为每个连接创建一个线程,然后销毁连接关闭的线程。
  4. 我承认选项2听起来对我来说是最好的解决方案,但我在所有这些方面都非常环保,所以我可能有点天真并且缺少一些明显的缺陷。此外,听起来似乎很难实施。

    PS:我正在考虑使用POCO C++ Libraries。建议任何替代库(如boost)都可以。然而,许多人说POCO的图书馆非常干净且易于理解。所以,我最好使用那个,所以我可以了解我正在使用的的方式。

5 个答案:

答案 0 :(得分:12)

当正确编写时,活动应用程序肯定会更好地扩展。这意味着

  • 永远不会阻止反应线程:
    • 任何阻塞都会严重降低服务器的性能,通常使用少量的反应式线程,因此阻塞也会很快导致死锁。
    • 没有互斥锁,因为它们可以阻塞,所以没有共享的可变状态。如果你需要共享状态,你必须用一个actor或类似的东西包装它,这样只有一个线程可以访问该状态。
  • 反应线程中的所有工作都应该是cpu绑定的
    • 所有IO必须是异步的,或者在不同的线程池中执行,结果会反馈到反应堆中。
    • 这意味着使用期货或回调来处理回复,如果您不习惯并且遵守纪律,这种代码风格很快就会变得无法维护。
  • 反应线程中的所有工作都应该很小
    • 为了保持服务器的响应能力,反应堆中的所有任务必须很小(以时间为界)
    • 在一台8核机器上你不能让8个长任务同时到达,因为在完成之前不会有其他工作开始
    • 如果任务需要很长时间,则必须将其分解(合作多任务处理)

应用程序中的任务由应用程序而非操作系统调度,这就是为什么它们可以更快并且使用更少的内存。当您编写一个Reactive应用程序时,您说您很了解问题域,以至于您可以更好地组织和安排此类工作,而不是操作系统可以调度线程以阻塞方式执行相同的工作。

我是反应式架构的忠实粉丝,但他们带来了成本。我不确定我会把我的第一个c ++应用程序写成反应,我通常会尝试一次学习一件事。

如果您决定使用反应式架构,请使用可以帮助您设计和构建代码的良好框架,否则最终会出现意大利面条。要寻找的是:

  • 工作单位是什么?
  • 添加新作品有多容易?它只能来自外部事件(例如网络请求)
  • 将工作分成小块是多么容易?
  • 处理这项工作的结果有多容易?
  • 将阻塞代码移动到另一个线程池并仍处理结果有多容易?

我不能为此推荐一个C ++库,我现在在ScalaAkka进行服务器开发,它为所有这些提供了一个优秀的可组合期货库,以保持代码清洁。

祝你好运学习C ++以及你做出的选择。

答案 1 :(得分:7)

选项2将最有效地占用您的硬件。这是经典文章,十年之久但仍然很好。

http://www.kegel.com/c10k.html

目前用于构建具有并发和异步等待的应用程序的最佳库组合是Boost Thread和Boost ASIO。您也可以尝试使用C ++ 11 std thread库和std mutex(但在很多情况下,Boost ASIO比互斥锁更好,只是总是回调到同一个线程而你不需要保护地区)。远离std future,导致它被破坏:

http://bartoszmilewski.com/2009/03/03/broken-promises-c0x-futures/

线程池中的最佳线程数是每个CPU核心一个线程。 8个核心 - > 8个主题。如果您认为您的线程池线程有时可能会调用阻塞操作,那么可能还需要额外的一些。

答案 2 :(得分:2)

FWIW,Poco支持自版本1.5.1以来的选项2(ParallelReactor

答案 3 :(得分:1)

我认为选项2是最好的选项。至于调整池大小,我认为池应该是自适应的。它应该能够生成更多线程(具有一些高硬限制)并在低活动时删除过多的线程。

答案 4 :(得分:1)

作为你所链接的类比(及其评论)的建议。这在一定程度上取决于应用现在你在这里建造的是游戏服务器。让我们分析一下。

游戏服务器(通常)执行大量I / O和相对较少的计算,因此它们远非100%的CPU应用程序。 另一方面,他们通常也会改变某些数据库中的值(“游戏世界”模型)。所有玩家都会对此数据库进行读写操作。这正是类比中的交叉问题。

因此,虽然您可以通过在单独的线程中处理I / O获得一些,但您也会因为让单独的线程访问同一个数据库并等待其锁定而失败。

因此,在您的情况下,选项1或2都可以接受。出于可伸缩性的原因,我不建议使用选项3。