【问题标题】:Will creating more threads than available processors have performance overhead?创建比可用处理器更多的线程会产生性能开销吗?
【发布时间】:2021-10-18 02:48:23
【问题描述】:

我的目标是处理线程内的 WebSocket 连接。如果我使用in a new Thread,服务器可以处理的WebSocket连接数是未知的。如果我使用in a Thread pool,那么服务器可以处理的WebSocket连接数就是线程池大小。

我不确定可用处理器和线程之间的相关性。 1 个处理器一次执行 1 个线程吗?

我的预期结果:创建比可用处理器更多的线程是不可取的,您应该重新设计处理 WebSocket 连接的方式。

在一个新的线程中

final Socket socket = serverSocket.accept();

new Thread(new WebSocket(socket, listener)).start();

在线程池中

final ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

final Socket socket = serverSocket.accept();

es.execute(new WebSocket(socket, listener));

为避免混淆,WebSocket 类是实现Runnable 的自定义类。据我所知,Java SE 没有 WebSocket 服务器,只有 WebSocket 客户端。

【问题讨论】:

  • stackoverflow.com/a/34689857/2478398 给出这个答案和它下面的答案。但实际上“这取决于”,并且取决于很多事情,例如您为每个线程分配的任务的 CPU 密集程度。
  • 你所说的“句柄”到底是什么意思,你在什么环境中运行你的套接字?您没有使用现有平台(例如 Spring)自动为您处理这些问题是否有特定原因?
  • @BeUndead 假设并发概念和巧妙的硬件设计,Executors.newFixedThreadPool(2000); 不会造成问题,因为 1. 操作系统将一次委托每个线程 Thread A - > Thread B - > Thread A 和 2. CPU 现在能够执行 2同时线程,假设我有 4 个内核,最好的线程池大小是 8 个线程,我只需要重新设计如何在 8 个线程中处理 WebSocket 连接e.g. 2000 keep alive connections。我说的对吗?
  • 理想情况下,创建一个线程池(无论您的常规流量大小如何)并允许它增长(最好缩小)以处理峰值,让 java+OS 担心其余的......但即使更理想的是,不要自己这样做,使用上面推荐的 @chrylis 之类的库。这些都是非常复杂的话题,人们需要数年时间才能弄清楚(我当然没有),甚至需要更多年才能很好地实施。
  • @chrylis-cautiouslyoptimistic- "handle" 在这种情况下,服务器如何接收传入的数据包数据和发送数据包数据,目前我将每个 WebSocket 连接放在 1 个唯一的线程中,但这意味着我必须与 HTTP 请求/响应不同,创建 100 个线程的线程池来处理 100 个活动连接。环境?我还是在我的笔记本本地使用它,我没有推送它,可能使用的是graalvm。我还没有完成 thr SSL 上下文。

标签: java multithreading sockets websocket


【解决方案1】:

制作线程。如果你愿意,一千。

在 CPU 内核级别,情况如下:

  • CPU 内核正在运行,为给定的 websocket 工作。
  • 很快内核就遇到了障碍:传入的数据束中的一半已经到达,其余的仍在通过网络电缆,因此 CPU 在到达之前无法继续工作。或者,CPU 内核正在运行的代码正在发送数据,但网卡的缓冲区已满,所以现在 CPU 内核必须等待该网卡找到它的方式,以便在有空间之前通过电缆发送另一个数据包。
  • 当然,如果有工作要做(例如,您有 10 个内核,并且同时连接了 15 个 Web 用户,那么您的网站上至少有 5 个用户正在等待) - 那么 CPU 应该只是开始玩弄它的拇指。它应该去做一些事情
  • 然后,在实践中,有一大堆内存不再相关(包含所有状态和其他“工作项”的所有内存,这些内存是为我们正在工作的 websocket 完成工作所必需的)开,但当前被网络“阻止”),以及一大堆不相关的内存现在变得相关(之前放在“拥有自己的”中的 websocket 连接的所有状态和工作内存有点超时并等待网络数据包到达' - 网络数据包已经到达,所以如果 CPU 内核有空工作,它现在可以开始工作了)。
  • 这被称为“上下文切换”,它贵得离谱,价值 500 多个周期。这也是完全无法避免的。您必须进行上下文切换。你无法避免它。这意味着要付出一定的代价,大约 500 次循环就值得上厕所。就是这样。

问题是,有两种方法可以支付该成本:您可以切换到另一个线程,这是各种上下文切换。或者,你有一个运行所谓的“异步”代码的单线程,它自己管理所有这些东西并跳到另一个工作去做,但是仍然有一个上下文切换。

具体来说,现在 CPU 根本无法与内存交互,过去十年也没有。它们只能与 CPU 缓存页面交互。机器代码实际上不再是真正“直接运行”了,而是在低于 CPU 注意到它即将运行一条触及某些内存的指令时,然后将映射该内存命令的级别(毕竟,CPU 不能再与它交互)总之,内存太慢了,无法等待)到缓存中的正确位置。它还会注意到您尝试使用机器代码访问的内存是否根本不在与该内核关联的缓存页面中,在这种情况下,它会触发页面未命中中断,从而导致 CPU 的内存子系统/memory 总线“驱逐一个页面”(将所有内容写回主内存),然后加载正确的页面,然后 CPU 才会继续。

这一切都发生在“幕后”,您不必编写代码来切换页面,CPU 会自动管理它。但这是一个沉重的代价。不像线程切换那么重,但几乎一样重。

结论:线程很好,有很多。它确保 CPU 在有工作要做时不会转动拇指。请注意,有许多博客文章颂扬异步的优点,声称线程“无法扩展”。他们错了。线程可以很好地扩展,并且异步代码也一直在为上下文切换付出代价。

如果您不知道,“异步代码”是一种尝试永不休眠的代码(永远不要做任何会等待的事情。因此,不要编写“getMeTheNextBlockOfBytesFromTheNetworkCard”,而是编写:“onceBytesAreAvailableRunThis(代码运行here)`). 在 java 中编写异步代码是可能的,但与使用线程相比非常困难。

即使在极少数情况下异步代码会取得重大胜利,Project Loom 也接近完成,这将使 java 能够拥有可以手动管理的类似线程的东西(所谓的纤维)。这就是 OpenJDK 为此选择的路线。从这个意义上说,即使您认为 async 是答案,也不是。而是等待 Project Loom 完成。如果您想了解更多信息,请阅读 What color is your function?callback hell。这两篇文章都不是特定于 java 的,但涵盖了异步中固有的一些更严重的问题。

【讨论】:

  • 这是否意味着CompletableFuture.runAsync( () - > { WebSocket websocket = new WebSocket(socket, listener); es.execute(websocket); });,我在final Socket socket = serverSocket.accept();之后使用这个,以防服务器在握手期间处于DDoS下并且http消息体很长,因此其他尝试连接的客户端不会受到影响。使用 CompletableFuture 是否会使 Java 应用程序遭受上下文切换成本?还有,你能解释一下500+ cycles worth的大小吗?
  • async 让 DDOS 服务器变得更容易,而不是更难。那个 runAsync 技巧几乎什么都不做。它当然不能阻止 DDoS 攻击。您无法避免上下文切换。时期。什么都做不到。
  • 500+ 个周期,例如:CPU 可以进行 500 次简单计算。 但事实就是如此,无论你怎么努力,都无法避免这个成本。 runAsync 也不会这样做。关键是:如果您阅读了一篇说异步避免它的博文,那么他们就误解了 CPU 的工作原理。
  • 异步使它变得更容易的原因:如果你在异步块中阻塞 anywhere,这意味着线程正在旋转它的拇指。如果我发现您忘记了(例如,您在某处进行了同步数据库调用),我只需要在 10 个同时连接上进入“阻止”模式,并且您的服务器的 CPU 被 100% 冻结,不响应任何其他内容。使用恶意请求循环线程要困难得多,而且更容易意识到这种情况正在发生(因为 CPU 负载会跃升至 100%)。
  • 另外,我认为你缺乏洞察力。您的 CPU 每秒可以运行数十亿次操作。那 500 感觉很贵,但在上下文中,我们谈论的是纳秒。只需编写最简单的代码,创建大量线程,调用阻塞代码,不用担心意外阻塞,而是担心算法效率低下。这样,您可以在最便宜的 AWS 虚拟服务器上处理数百个同时连接,没问题。
猜你喜欢
  • 2015-02-03
  • 2023-04-04
  • 2011-04-10
  • 2020-08-30
  • 1970-01-01
  • 2013-02-09
  • 2010-10-07
  • 2016-09-26
  • 2011-01-15
相关资源
最近更新 更多