创建比可用处理器更多的线程会产生性能开销吗?
Will creating more threads than available processors have performance overhead?
我的目标是在线程内处理 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
class 是实现 Runnable
的自定义 class。据我所知,JavaSE没有WebSocket服务器,只有WebSocket客户端。
制作线程。想要一千
在 CPU 核心级别,这是正在发生的事情:
- CPU 核心正在运行,为给定的 websocket 工作。
- 很快核心 运行 就遇到了障碍:传入的数据束中有一半已经到达,其余的仍在沿着网络电缆传输,因此 CPU在它到达之前无法继续。或者,CPU核心是运行ning的代码正在向外发送数据,但是网卡的缓冲区已满,所以现在CPU核心必须等待那个网卡找到它在有空间之前通过电缆发送另一个数据包的方式。
- 当然,如果有工作要做(比如,您的盒子中有 10 个核心,并且同时连接了 15 个网络用户,那么您的网站现在至少有 5 个用户在等待)- 那么CPU 应该 而不是 只是开始摆弄它的拇指。它应该去做点什么。
- 然后,在实践中,有一大堆内存是相关的,但不再是相关的(所有包含所有状态的内存和其他 'working items' 为 websocket 完成工作所必需的内存我们正在处理,但目前网络 'blocked'),一大堆不相关的内存现在变得相关(之前放入的 websocket 连接的所有状态和工作内存'have yourself a bit of a timeout and wait around for the network packet to arrive' - 网络数据包已经到达,因此如果 CPU 核心可以自由工作,它现在可以开始工作了。
- 这叫做 'context switch',贵得离谱,价值 500 多个周期。这也是完全无法避免的。您必须进行上下文切换。你无法避免它。这意味着要付出代价,大约 500 个周期值得去厕所。就是这样。
事实是,有两种方法可以支付该费用:您可以切换到另一个线程,这是各种上下文切换。或者,您有一个单线程 运行 宁所谓的 'async' 代码,它自己管理所有这些东西并跳到另一个工作去做,但仍然有一个上下文切换。
具体来说,现在 CPUs 根本无法与记忆互动,过去十年也一样。他们只能与 CPU 缓存页面交互。机器代码实际上不再是真正的 'run directly' 了,取而代之的是在那个层级之下 CPU 注意到它即将 运行 一条指令触及一些内存然后将映射该内存命令(之后总而言之,CPUs 根本无法再与其交互,内存 far 太慢无法等待它)到缓存中的正确位置。它还会注意到您尝试使用机器代码访问的内存是否根本不在与该核心关联的缓存页面中,在这种情况下,它会触发页面未命中中断,从而导致您的内存子系统 CPU/memory 总线到 'evict a page'(将所有内容写回主内存),然后加载到正确的页面,然后 CPU 才会继续。
这一切都会发生 'under the hood',您无需编写代码来切换页面,CPU 会自动管理它。但这是一个沉重的代价。不像线程开关那么重,但几乎一样重。
结论:线程很好,有很多线程。它确保 CPUs 在有工作要做时不会胡乱摆弄他们的拇指。请注意,有许多博客 post 赞美异步的优点,声称线程 'do not scale'。他们错了。线程可以很好地扩展,异步代码也始终支付上下文切换的成本。
如果你不知道,'async code' 是试图永不休眠的代码(永远不会做任何会等待的事情。所以,而不是写 'getMeTheNextBlockOfBytesFromTheNetworkCard',你会写: “onceBytesAreAvailableRunThis(代码在此处)`)。在 java 中编写异步代码是可能的,但与使用线程相比非常困难。
即使在极少数情况下,异步代码会取得重大胜利,Loom 项目也接近完成,这将授予 java 拥有您可以手动管理的类似线程的能力(所以-称为纤维)。这是 OpenJDK 为此选择的路线。从这个意义上说,即使您认为异步是答案,但事实并非如此。相反,等待 Project Loom 完成。如果您想阅读更多内容,请阅读 What color is your function?, and callback hell。 post 都不是 java 特有的,但涵盖了异步中固有的一些更严重的问题。
我的目标是在线程内处理 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
class 是实现 Runnable
的自定义 class。据我所知,JavaSE没有WebSocket服务器,只有WebSocket客户端。
制作线程。想要一千
在 CPU 核心级别,这是正在发生的事情:
- CPU 核心正在运行,为给定的 websocket 工作。
- 很快核心 运行 就遇到了障碍:传入的数据束中有一半已经到达,其余的仍在沿着网络电缆传输,因此 CPU在它到达之前无法继续。或者,CPU核心是运行ning的代码正在向外发送数据,但是网卡的缓冲区已满,所以现在CPU核心必须等待那个网卡找到它在有空间之前通过电缆发送另一个数据包的方式。
- 当然,如果有工作要做(比如,您的盒子中有 10 个核心,并且同时连接了 15 个网络用户,那么您的网站现在至少有 5 个用户在等待)- 那么CPU 应该 而不是 只是开始摆弄它的拇指。它应该去做点什么。
- 然后,在实践中,有一大堆内存是相关的,但不再是相关的(所有包含所有状态的内存和其他 'working items' 为 websocket 完成工作所必需的内存我们正在处理,但目前网络 'blocked'),一大堆不相关的内存现在变得相关(之前放入的 websocket 连接的所有状态和工作内存'have yourself a bit of a timeout and wait around for the network packet to arrive' - 网络数据包已经到达,因此如果 CPU 核心可以自由工作,它现在可以开始工作了。
- 这叫做 'context switch',贵得离谱,价值 500 多个周期。这也是完全无法避免的。您必须进行上下文切换。你无法避免它。这意味着要付出代价,大约 500 个周期值得去厕所。就是这样。
事实是,有两种方法可以支付该费用:您可以切换到另一个线程,这是各种上下文切换。或者,您有一个单线程 运行 宁所谓的 'async' 代码,它自己管理所有这些东西并跳到另一个工作去做,但仍然有一个上下文切换。
具体来说,现在 CPUs 根本无法与记忆互动,过去十年也一样。他们只能与 CPU 缓存页面交互。机器代码实际上不再是真正的 'run directly' 了,取而代之的是在那个层级之下 CPU 注意到它即将 运行 一条指令触及一些内存然后将映射该内存命令(之后总而言之,CPUs 根本无法再与其交互,内存 far 太慢无法等待它)到缓存中的正确位置。它还会注意到您尝试使用机器代码访问的内存是否根本不在与该核心关联的缓存页面中,在这种情况下,它会触发页面未命中中断,从而导致您的内存子系统 CPU/memory 总线到 'evict a page'(将所有内容写回主内存),然后加载到正确的页面,然后 CPU 才会继续。
这一切都会发生 'under the hood',您无需编写代码来切换页面,CPU 会自动管理它。但这是一个沉重的代价。不像线程开关那么重,但几乎一样重。
结论:线程很好,有很多线程。它确保 CPUs 在有工作要做时不会胡乱摆弄他们的拇指。请注意,有许多博客 post 赞美异步的优点,声称线程 'do not scale'。他们错了。线程可以很好地扩展,异步代码也始终支付上下文切换的成本。
如果你不知道,'async code' 是试图永不休眠的代码(永远不会做任何会等待的事情。所以,而不是写 'getMeTheNextBlockOfBytesFromTheNetworkCard',你会写: “onceBytesAreAvailableRunThis(代码在此处)`)。在 java 中编写异步代码是可能的,但与使用线程相比非常困难。
即使在极少数情况下,异步代码会取得重大胜利,Loom 项目也接近完成,这将授予 java 拥有您可以手动管理的类似线程的能力(所以-称为纤维)。这是 OpenJDK 为此选择的路线。从这个意义上说,即使您认为异步是答案,但事实并非如此。相反,等待 Project Loom 完成。如果您想阅读更多内容,请阅读 What color is your function?, and callback hell。 post 都不是 java 特有的,但涵盖了异步中固有的一些更严重的问题。