如何为C ++多线程应用程序选择正确的线程数?

时间:2016-04-20 06:15:28

标签: c++ linux multithreading performance cpu

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

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

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

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

因此,线程池是应用程序的瓶颈。为了增加服务器上并发播放器的数量,谁将在没有延迟的情况下播放,我需要增加线程池中的线程数。

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

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

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

5 个答案:

答案 0 :(得分:2)

很少有问题。

  

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

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

  

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

再次,没有异步IO,这个架构是90多个服务器端架构(a-la Apache风格)。为了获得最佳性能,您的线程应该只执行CPU绑定任务,不应等待任何IO操作。

  

因此,线程池是应用程序的瓶颈。增加数量   服务器上的并发玩家,他们将在没有滞后的情况下玩游戏   增加线程池中的线程数。

死错了。阅读有关10k并发问题的内容。

  

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

因此,关于线程数量作为核心数量的轶事仅在您的线程 cpu绑定任务时才有效,并且它们永远不会被阻止,并且它们100%使用cpu任务停顿。如果你的线程也被锁或IO动作阻止,这个事实就会中断。

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

Apache样式架构:
拥有固定大小的线程池。将线程分配给连接队列中的每个连接。非异步IO。
专业人士:非。
缺点:吞吐量极差

NGNix / Node.js架构:
具有单线程 - 多处理应用程序。使用异步IO 优点:简单的架构,消除了多线程问题。服务于静态数据的服务器非常出色 缺点:如果进程必须砍掉数据,那么在进程之间通过serilizeing-desirilizing数据来消耗大量的CPU时间。此外,如果正确完成,多线程应用程序可以提高性能。

现代.Net architecure:
具有多线程单声道处理应用程序。使用异步IO 专业人士:如果做得正确,表演可以爆炸!
缺点:调整多线程应用程序并在不破坏红外数据的情况下使用它有点棘手。

总而言之,我认为在您的特定情况下,您应该仅使用异步IO +具有线程池,线程数等于核心数。

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

答案 1 :(得分:1)

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

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

这样做的另一个好处是,您可以使用与分析器相同的压力测试来确定是否可以从实现中提取更多性能。

答案 2 :(得分:1)

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

我建议做试验,找出可以达到最佳性能的方法。

答案 3 :(得分:1)

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

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

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

答案 4 :(得分:1)

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

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

如果您的客户正在进行I / O(网络;文件;甚至页面交换)或任何其他阻塞您的线程的操作,那么将需要设置更多的线程,因为即使cpu中的某些线程也会被锁定是可用的。

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

但要注意:如果客户端链接到单个线程(线程a处理客户端1,2,3,线程B处理客户端4,5,6)增加线程池将有所帮助,但是如果客户端仍然存在零星滞后两个客户端事件应该由同一个线程处理。