【问题标题】:How to choose correct number of threads for C++ multithread application?如何为 C++ 多线程应用程序选择正确的线程数?
【发布时间】:2016-04-20 06:15:28
【问题描述】:

我是 C++ 后端开发人员。我为实时游戏开发服务器端。因此,应用程序架构如下所示:

1) 我有类客户端,它处理来自游戏客户端的请求。请求示例:登录、在商店购买东西(游戏内部商店)或制作一些东西。此客户端还处理来自游戏客户端的用户输入事件(通常是事件,当玩家玩游戏时,它会在一秒钟内从游戏客户端向服务器发送十次)。

2) 我有线程池。当游戏客户端连接到服务器时,我创建客户端实例并将它们绑定到池中的线程之一。所以,我们有一对多的关系:一个线程 - 许多客户。循环用于选择线程进行绑定。

3) 我使用 Libev 来管理服务器内的所有事件。这意味着当客户端实例通过网络从游戏客户端接收一些数据,或者处理一些请求,或者试图通过网络向游戏客户端发送一些数据时,他锁定了hi的线程。当他做一些东西时,其他客户端共享同一个线程将被锁定。

因此,线程池是应用程序的瓶颈。为了增加服务器上的并发玩家数量,谁可以毫无延迟地玩,我需要增加线程池中的线程数。

现在应用程序在具有 24 个逻辑 cpu 的服务器上工作(cat /proc/cpuinfo 说它)。我将线程池大小设置为 24(1 个处理器 - 1 个线程)。这意味着,在当前在线 2000 名玩家的情况下,每个线程都服务于大约 84 个客户端实例。 top 说处理器的使用率不到 10%。

现在问题。如果我增加线程池中的线程数是增加还是减少服务器性能(上下文切换开销与每个线程锁定的客户端)?

UPD 1)服务器有异步IO(libev + epoll),所以当我说客户端在发送和接收数据时被锁定时,我的意思是应对缓冲区。 2)服务器也有后台线程用于慢任务:数据库操作,硬计算操作,...

【问题讨论】:

  • 根据经验:运行线程数应大致等于 CPU 内核数。
  • 好的,但是如果有 10 个线程空闲并等待某些事件,并且 14 个线程过载,情况会怎样。我认为如果服务器只有 10% 的负载,我可以增加线程数,因为其中一些会休眠。
  • “线程池是应用程序的瓶颈”是一个大胆的主张,是什么让你这么认为?无论如何,你问你系统的性能是什么。您应该做的第一件事是设置测量方法。首先,你的问题已经过时了。其次,您可以衡量自己在提高性能方面的尝试。
  • 是的,我认为在不同情况下衡量性能是个好方法。我会花一些时间来做这件事。在此之前我想知道专家的意见,因为我在stackoverflow上提问。 (我办公室里没有人可以回答这个问题)
  • 当线程被锁定时,客户端是否发送、接收或执行任何其他阻塞操作?即使 CPU 或多或少处于空闲状态,这也可能会锁定线程。如果是这种情况,线程池中的更多线程将是一个解决方案。

标签: c++ linux multithreading performance cpu


【解决方案1】:

问题很少。

2) 我有线程池。当游戏客户端连接到我创建的服务器时 客户端实例并将它们绑定到池中的线程之一。所以,我们有 一对多关系:一个线程 - 许多客户。使用循环 选择线程进行绑定。

您在任何一点中都没有提到异步 IO,我相信您真正的瓶颈不是线程数,而是线程由于 IO 操作而被阻塞的事实。通过使用异步 IO(这不是另一个线程上的 IO 操作)- 服务器的吞吐量会大幅增加。

3) 我使用 Libev 来管理服务器内的所有事件。意思是什么时候 客户端实例通过网络从游戏客户端接收一些数据,或者 处理一些请求,或者尝试通过网络发送一些数据到 游戏客户端他锁定了hi的线程。当他做一些其他的东西时 共享同一线程的客户端将被锁定。

再一次,如果没有异步 IO,这个架构非常类似于 90 年代的服务器端架构(a-la Apache 风格)。为了获得最佳性能,您的线程应该只执行 CPU 绑定任务,并且不应等待任何 IO 操作。

因此,线程池是应用程序的瓶颈。增加数量 服务器上的并发玩家,我需要的玩家可以毫无延迟地玩 增加线程池中的线程数。

大错特错。阅读有关 10k 并发问题的信息。

现在问题。如果我增加线程池中的线程数是它 提高或降低服务器性能(上下文切换开销与 每个线程锁定的客户端)?

因此,关于线程数作为内核数的轶事仅在您的线程执行 cpu 绑定任务并且它们从未被阻塞并且它们100% 与cpu 任务同步时才有效。如果你的线程也被锁或 IO 操作阻塞,那么这个事实就被打破了。

如果我们看看常见的服务器端架构,我们可以确定我们需要的最佳设计

Apache 风格架构:
具有固定大小的线程池。为连接队列中的每个连接分配一个线程。非异步 IO。
优点: 非。
缺点: 吞吐量极差

NGNix/Node.js 架构:
具有单线程 - 多处理应用程序。使用异步 IO。
优点:消除多线程问题的简单架构。非常适合提供静态数据的服务器。
缺点:如果进程必须共享数据,则会在进程之间的序列化-传递-去化数据上消耗大量 CPU 时间。此外,如果操作正确,多线程应用程序可以提高性能。

现代 .Net 架构:
具有多线程单处理应用程序。使用异步 IO。
优点:如果操作正确,性能会爆炸!
缺点:调整多线程应用程序并在不使用它的情况下使用它有点棘手损坏共享数据。

总而言之,我认为在您的具体情况下,您应该明确地仅使用异步 IO + 拥有一个线程数等于内核数的线程池。

如果您使用的是 Linux,Facebook 的 Proxygen 可以为您管理我们讨论的所有内容(带有异步 IO 的多线程代码)。嘿,facebook 正在使用它!

【讨论】:

  • 是的。您对异步 IO 的权利。我在下面的 cmets 中写到它。我有额外的线程池(称为后台线程),它服务于慢速任务并且不会阻塞客户端的线程。所以,如果我们需要做一些小事——我会在客户端的线程池中处理它。
  • 这也不是管理操作系统资源的好方法。例如,Linux 上的默认线程堆栈大小为 8MB。 IO 操作有 100 个线程仅意味着: 1. 您最多可以执行 100 个 IO 并发操作。 2. 你没有使用 800 MB。切换到异步 IO。
  • 好的。我明白。遗留代码中的问题。基础架构是在我之前建立的。我努力让它变得更好。
  • @DavidHaim:不,Linux 不使用每个线程 8 MB 的 RAM。它保留了地址空间,但在当今的 64 位世界中,800 MB 的地址空间是微不足道的。实际 RAM 使用量与实际堆栈使用量相关,通常以 kB 为单位。您的 100 个线程可能使用 1MB 的 RAM,而不是 800 个。
  • @DavidHaim,我使用 Linux,但不能使用 Proxygen。我的服务器使用自定义二进制协议,经过优化以减少流量。
【解决方案2】:

很多因素都会影响整体性能,包括每个客户端每个线程需要做多少事情,需要多少跨线程通信,线程之间是否存在资源争用等等。最好的办法是:

  • 确定要测量的性能参数并确保对它们进行检测 - 您提到了延迟,因此您需要一种机制来测量最坏情况下的延迟和/或从服务器端跨所有客户端的延迟分布。李>
  • 构建压力情景。这可以像回放真实客户端行为或随机行为的工具一样简单,但越能代表真实负载越好。
  • 对处于压力下的服务器进行基准测试并更改线程数(甚至更彻底地更改设计),并查看哪种设计或配置可以将延迟降至最低。

这具有额外的好处,您可以将相同的压力测试与分析器一起使用,以确定您是否可以从您的实施中提取更多性能。

【讨论】:

    【解决方案3】:

    最佳线程数通常等于机器中的内核数或内核数的两倍。为了获得可能的最大吞吐量,线程之间必须存在最小的争用点。这个数字,即竞争点的数量在核心数量的值和核心数量的两倍之间浮动。

    我建议您进行试验并找出可以达到最佳性能的方法。

    【讨论】:

      【解决方案4】:

      从每个核心有一个线程的想法开始可能会很好。

      此外,在某些情况下,计算 WCET(最坏情况执行时间)是一种定义哪种配置更快(内核并不总是具有相同频率)的方法。您可以使用计时器轻松测量它(从函数开始到结束,并减去值以获得以毫秒为单位的结果。)

      就我而言,我还必须处理消耗,因为它是一个嵌入式系统。一些工具允许测量 CPU 消耗,从而决定在这种特定情况下哪种配置最有趣。

      【讨论】:

        【解决方案5】:

        最佳线程数取决于您的客户端使用 CPU 的方式。

        如果 cpu 是唯一的瓶颈,并且每个运行线程的内核始终处于最高负载,那么将线程数设置为内核数是个好主意。

        如果您的客户端正在执行 I/O(网络;文件;甚至页面交换)或任何其他阻塞线程的操作,则有必要设置更多的线程数,因为即使 cpu可用。

        在您的情况下,我认为这是第二种情况。线程被锁定是因为有 24 个客户端事件处于活动状态,但只使用了 10% 的 cpu(因此线程处理的事件浪费了 90% 的 cpu 资源)。如果是这种情况,最好将线程数提高到 240(内核数 * 100 / 平均负载),这样另一个线程就可以在空闲的 cpu 上运行。

        但请注意:如果客户端链接到单个线程(线程 a 处理客户端 1、2、3,线程 B 处理客户端 4、5、6),增加线程池会有所帮助,但如果两个客户端事件应该由同一个线程处理。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-02-09
          • 1970-01-01
          • 2010-11-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-26
          • 1970-01-01
          相关资源
          最近更新 更多