如何为C ++multithreading应用程序select正确的线程数?

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

1)我有类客户端,它处理来自游戏客户端的请求。 请求示例:login,购买商店内容(游戏内部商店)或制作一些东西。 另外这个客户端处理来自游戏客户端的用户input事件(当玩家在游戏中玩时,它经常是事件,从游戏客户端到服务器发送十次)。

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

3)我使用Libev来pipe理服务器内的所有事件。 当Client实例通过networking从游戏客户端接收一些数据,或者处理一些请求,或者试图通过networking向游戏客户端发送一些数据时,它就是这个意思。 当他做一些东西,其他客户,共享相同的线程将被locking。

所以,线程池是应用程序的瓶颈。 为了增加服务器上的并发播放器的数量,谁会玩的时间不长,我需要增加线程池中的线程数。

现在应用程序工作在24逻辑cpus服务器( cat /proc/cpuinfo说)。 我把线程池大小设置为24(1个处理器 – 1个线程)。 这意味着,当前的在线2000玩家每个线程服务大约84个客户端实例。 top说处理器使用less于百分之十。

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

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

那么很少有问题。

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

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

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

再次,没有异步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正在使用它!

影响整体性能的因素很多,包括每个线程每个客户端要做多少线程,需要多少线程交互,线程间是否存在资源竞争等等。 最好的做法是:

  • 决定你要衡量的性能参数,并确保你有他们的仪器 – 你提到的滞后,所以你需要一个机制,从服务器端测量所有客户端的最坏情况滞后和/或滞后分布。
  • 建立一个压力情景。 这可以像重放真实客户行为或随机行为的工具一样简单,但是实际负载越具代表性越好。
  • 在受压情况下对服务器进行基准测试,并更改线程数量(甚至更根本地改变设计),并查看哪个设计或配置导致了最小的延迟。

这还有一个额外的好处,就是你可以使用相同的压力测试和一个分析器来确定你是否可以从你的实现中提取更多的性能。

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

我会建议做试验,找出最佳的表现方式。

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

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

在我的情况下,我还必须在消费方面进行研究,因为这是一个嵌入式系统。 一些工具允许测量CPU消耗,并因此决定哪种配置在这种特定情况下是最有趣的。

线程的最佳数量取决于你的客户如何使用CPU。

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

如果你的客户正在做I / O(网络,文件,甚至页面交换)或其他任何阻塞你的线程的操作,那么就需要设置更多的线程,因为即使CPU可用,它们中的一些也将被锁定。

在你的情况下,我认为这是第二种情况。 线程被锁定,因为24个客户端事件是活动的,但只使用CPU的10%(因此线程处理的事件正在浪费其cpus资源的90%)。 如果是这样的话,将线程数提高到240(核心数量×100 /平均负载)是一个好主意,所以另一个线程可以在闲置的CPU上运行。

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