作为一个侧面项目,我正在为一个我曾经玩过的古老游戏编写服务器.我试图让服务器尽可能松散耦合,但我想知道什么是多线程的好设计决策.目前我有以下一系列行动:
启动(创建) - >
服务器(侦听客户端,创建) - >
客户端(侦听命令并发送期间数据)
我假设平均有100个客户,因为这是游戏任何给定时间的最大值.对于整个事物的线程,什么是正确的决定?我目前的设置如下:
服务器上的1个线程侦听新连接,在新连接上创建客户端对象并再次开始侦听.
客户端对象有一个线程,侦听传入命令并发送定期数据.这是使用非阻塞套接字完成的,因此它只检查是否有可用的数据,处理该数据然后发送已排队的消息.登录在发送 - 接收周期开始之前完成.
一个线程(现在)用于游戏本身,因为我认为从架构上讲,它与整个客户端 - 服务器部分是分开的.
这将导致总共102个线程.我甚至考虑给客户端2个线程,一个用于发送,一个用于接收.如果我这样做,我可以在接收器线程上使用阻塞I/O,这意味着线程在平均情况下将大部分处于空闲状态.
我主要担心的是,通过使用这么多线程,我将占用资源.我并不担心竞争条件或死锁,因为这是我无论如何都要处理的事情.
我的设计是这样设置的,我可以使用单个线程进行所有客户端通信,无论它是1还是100.我已将通信逻辑与客户端对象本身分开,因此我可以实现它而无需重写很多代码.
主要问题是:在应用程序中使用超过200个线程是错误的吗?它有优势吗?我正在考虑在多核机器上运行它,它会像这样多个核心占用很多优势吗?
谢谢!
在所有这些线程中,大多数线程通常会被阻塞.我不认为连接速度超过每分钟5次.来自客户端的命令很少会出现,我平均说每分钟20个.
按照我得到的答案(上下文切换是我正在考虑的性能打击,但我不知道,直到你指出它,谢谢!)我想我会采用一个听众,一个方法接收者,一个发送者和一些杂项;-)
使用事件流/队列和线程池来维持平衡; 这将更好地适应可能具有更多或更少核心的其他机器
一般来说,比核心更多的活动线程会浪费时间上下文切换
如果您的游戏包含许多短操作,则循环/循环事件队列将提供比固定数量的线程更好的性能
简单地回答这个问题,在今天的硬件上使用200个线程是完全错误的.
每个线程占用1 MB的内存,因此在开始做任何有用的事情之前,你需要占用200MB的页面文件.
通过各种方式将操作分解为可以安全地在任何线程上运行的小块,但是将这些操作放在队列上并且具有为这些队列服务的固定的有限数量的工作线程.
更新:浪费200MB重要吗?在32位机器上,它占整个理论地址空间的10% - 没有其他问题.在一台64位的机器上,它听起来像是理论上可用的海洋中的一滴,但实际上它仍然是一个非常大的存储块(或者更确切地说,大量相当大的存储块)被毫无意义地保留在应用程序,然后必须由操作系统管理.它具有围绕每个客户端的有价值信息的效果,其中包含大量无用的填充,这会破坏本地性,破坏操作系统和CPU尝试将频繁访问的内容保存在最快的缓存层中.
无论如何,内存浪费只是精神错乱的一部分.除非你有200个内核(以及一个能够使用的操作系统),否则你实际上没有200个并行线程.你有(比方说)8个核心,每个核心在25个线程之间疯狂切换.天真的你可能会认为,由于这个原因,每个线程都会遇到运行速度慢25倍的核心.但它实际上要比这更糟糕 - 操作系统花费更多的时间从核心中取出一个线程并在其上放置另一个线程("上下文切换"),而不是实际允许代码运行.
只要看看任何知名的成功设计如何解决这类问题.CLR的线程池(即使你没有使用它)就是一个很好的例子.假设每个核心只有一个线程就足够了.它允许创建更多,但只是为了确保最终完成设计糟糕的并行算法.它拒绝每秒创建超过2个线程,因此它通过减慢线程贪婪算法来有效地惩罚它们.