Deja de preocuparte por el tamaño del grupo de subprocesos y la cantidad de subprocesos. No existe una fórmula fija.

Es posible que muchas personas hayan visto una teoría sobre cómo establecer el número de subprocesos:
  • Programas intensivos de CPU: número de núcleos + 1
  • Programas intensivos de E/S - número de núcleos * 2
No, no, ¿alguien realmente planifica el número de subprocesos según esta teoría?

Pequeña prueba de número de subprocesos y utilización de CPU

Dejando de lado algunos sistemas operativos y principios informáticos, hablemos de una teoría básica (no hay necesidad de preocuparse por si es rigurosa, solo para una fácil comprensión):
Un núcleo de CPU solo puede ejecutar las instrucciones de un subproceso por unidad de tiempo.
Entonces, en teoría, un subproceso solo necesita seguir ejecutando instrucciones para alcanzar la utilización completa de un núcleo.
Escribamos un ejemplo de ejecución en un bucle sin fin para verificar:
Entorno de prueba: AMD Ryzen 5 3600, 6 núcleos, 12 subprocesos
  
  
  
  
  
public class CPUUtilizationTest {  public static void main(String[] args) {    //死循环,什么都不做    while (true){    }  }}
Después de este ejemplo, echemos un vistazo a la utilización actual de la CPU:
Como puede ver en la imagen, mi utilización del núcleo número 3 se ha utilizado por completo.
Según la teoría anterior, ¿debería intentar abrir algunos hilos más?
  
  
  
  
  
public class CPUUtilizationTest {  public static void main(String[] args) {
for (int j = 0; j < 6; j++) { new Thread(new Runnable() { @Override public void run() { while (true){ } } }).start(); } }}
En cuanto a la utilización de la CPU en este momento, las tasas de utilización de varios núcleos el 1/2/5/7/9/11 ya están completas:
Entonces, si se abren 12 subprocesos, ¿se utilizarán por completo todos los núcleos? La respuesta debe ser sí:
Si continúo aumentando la cantidad de subprocesos en el ejemplo anterior a 24 subprocesos en este momento, ¿cuál será el resultado?
Como puede ver en la imagen de arriba, la utilización de la CPU es la misma que en el paso anterior, todavía 100% para todos los núcleos, pero en este momento la carga ha aumentado de 11.x a 22.x (para la explicación de la carga promedio). , consulte https://scoutapm.com/blog/understanding-load-averages ), lo que indica que la CPU está más ocupada en este momento y las tareas del subproceso no se pueden ejecutar a tiempo.
Las CPU modernas son básicamente multinúcleo. Por ejemplo, la AMD 3600 que probé aquí tiene 6 núcleos y 12 subprocesos (hyper-threading). Simplemente podemos pensar en ella como una CPU de 12 núcleos. Entonces mi CPU puede hacer 12 cosas al mismo tiempo sin molestarse entre sí.
Si la cantidad de subprocesos a ejecutar es mayor que la cantidad de núcleos, entonces el sistema operativo debe programarlo. El sistema operativo asigna recursos de intervalo de tiempo de CPU a cada subproceso y luego cambia continuamente para lograr el efecto de ejecución "paralela".
¿Pero es esto realmente más rápido? Como se puede ver en el ejemplo anterior, un subproceso puede utilizar completamente la utilización de un núcleo . Si cada subproceso es muy "autoritario" y sigue ejecutando instrucciones sin darle tiempo de inactividad a la CPU, y la cantidad de subprocesos que se ejecutan al mismo tiempo es mayor que la cantidad de núcleos de la CPU, hará que el sistema operativo cambie la ejecución del subproceso . con más frecuencia para garantizar que todos los subprocesos puedan ejecutarse.
Sin embargo, el cambio tiene un costo: cada cambio irá acompañado de operaciones como actualizaciones de datos de registro y actualizaciones de la tabla de páginas de memoria . Aunque el costo de un cambio es insignificante en comparación con las operaciones de E/S, si hay demasiados subprocesos, los cambios de subprocesos son demasiado frecuentes o incluso el tiempo de conmutación por unidad de tiempo es mayor que el tiempo de ejecución del programa, provocará un exceso de CPU. Recursos: desperdiciados en cambiar de contexto en lugar de ejecutar el programa, la ganancia supera la ganancia.
El ejemplo anterior de ejecución en un bucle sin fin es demasiado extremo y es poco probable que dicho programa exista en circunstancias normales.
La mayoría de los programas tendrán algunas operaciones de E/S cuando se estén ejecutando, que pueden ser leer y escribir archivos, enviar y recibir mensajes a través de la red, etc. Estas operaciones de E/S deben esperar comentarios mientras están en progreso. Por ejemplo, al leer y escribir en la red, debe esperar a que se envíen o reciban mensajes. Durante este proceso de espera, el hilo está en estado de espera y la CPU no funciona. En este momento, el sistema operativo programará la CPU para ejecutar instrucciones de otros subprocesos, aprovechando así perfectamente el período de inactividad de la CPU y mejorando la utilización de la CPU.
En el ejemplo anterior, el programa sigue repitiendo y no hace nada, y la CPU tiene que seguir ejecutando instrucciones, sin dejar casi tiempo libre. ¿Qué pasa si se inserta una operación de E/S y la CPU está inactiva durante la operación de E/S? ¿Qué pasará con la utilización de la CPU? Veamos primero los resultados en un solo hilo:
  
  
  
  
  
public class CPUUtilizationTest {  public static void main(String[] args) throws InterruptedException {
for (int n = 0; n < 1; n++) { new Thread(new Runnable() { @Override public void run() { while (true){ //每次空循环 1亿 次后,sleep 50ms,模拟 I/O等待、切换 for (int i = 0; i < 100_000_000l; i++) { } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }}
Vaya, la única tasa de utilización del núcleo No. 9 es solo del 50%, en comparación con el 100% anterior sin dormir, ya es la mitad menor. Ahora ajuste el número de hilos a 12 y vea:
La tasa de utilización de un solo núcleo es de aproximadamente 60, que no es muy diferente del resultado de un solo subproceso en este momento. La utilización de la CPU aún no se ha alcanzado por completo. Ahora aumente el número de subprocesos a 18:
En este momento, la utilización de un solo núcleo es cercana al 100%. Se puede ver que cuando hay E / S y otras operaciones en el subproceso que no ocupan recursos de la CPU, el sistema operativo puede programar la CPU para ejecutar más subprocesos al mismo tiempo.
Ahora aumente la frecuencia de los eventos de E/S y reduzca el número de bucles a la mitad, 50_000_000, los mismos 18 subprocesos:
En este momento, la tasa de utilización de cada núcleo es solo de alrededor del 70%.

Un breve resumen del número de subprocesos y la utilización de la CPU

El ejemplo anterior es solo un auxiliar. Para comprender mejor la relación entre el número de subprocesos/comportamiento del programa/estado de la CPU, resumamos brevemente:
  1. Un subproceso extremo (cuando ejecuta constantemente operaciones "informáticas") puede aprovechar al máximo la utilización de un solo núcleo. Una CPU de varios núcleos solo puede ejecutar un número máximo de subprocesos "extremos" igual al número de núcleos al mismo tiempo.
  2. Si cada subproceso es tan "extremo" y la cantidad de subprocesos que se ejecutan al mismo tiempo excede la cantidad de núcleos, provocará cambios innecesarios, hará que la carga sea demasiado alta y solo hará que la ejecución sea más lenta.
  3. Durante operaciones de pausa como E/S, la CPU está en un estado inactivo y el sistema operativo programa la CPU para ejecutar otros subprocesos, lo que puede mejorar la utilización de la CPU y ejecutar más subprocesos al mismo tiempo.
  4. Cuanto mayor sea la frecuencia de los eventos de E/S o mayor sea el tiempo de espera/pausa, mayor será el tiempo de inactividad de la CPU y cuanto menor sea la tasa de utilización, el sistema operativo puede programar la CPU para ejecutar más subprocesos.

La fórmula para la planificación del número de hilos.

El presagio anterior es todo para ayudar a comprender. Ahora veamos la definición en el libro. "Programación concurrente de Java en la práctica" presenta una fórmula para calcular el número de subprocesos:
Si desea que el programa se ejecute hasta la utilización de CPU objetivo, la fórmula para la cantidad de subprocesos necesarios es:
La fórmula es muy clara, ahora probémosla con el ejemplo anterior:
Si espero una utilización objetivo del 90% (multinúcleo 90), entonces la cantidad de subprocesos necesarios es:
Número de núcleos 12 * Tasa de utilización 0,9 * (1 + 50 (tiempo de suspensión)/50 (ciclo 50_000_000 que consume mucho tiempo)) ≈ 22
Ahora ajuste el número de hilos a 22 y vea los resultados:
La utilización de la CPU ahora es de aproximadamente 80+, lo que está cerca de las expectativas. Debido a la cantidad excesiva de subprocesos, cierta sobrecarga de cambio de contexto y la falta de casos de prueba rigurosos, es normal que la utilización real sea menor.
Al cambiar la fórmula, también puede calcular la utilización de la CPU por la cantidad de subprocesos:
Número de subprocesos 22 / (Número de núcleos 12 * (1 + 50 (tiempo de suspensión) / 50 (ciclo 50_000_000 que consume mucho tiempo))) ≈ 0,9
Aunque la fórmula es buena, en un programa real, generalmente es difícil obtener el tiempo de espera y el tiempo de cálculo precisos, porque el programa es complejo y no solo "calcula" . Habrá mucha lectura y escritura de memoria, cálculos, E/S y otras operaciones compuestas en un fragmento de código. Es difícil obtener con precisión estos dos indicadores, por lo que calcular el número de subprocesos solo mediante fórmulas es demasiado ideal.

Número de hilos en el programa real.

Entonces, en programas reales, o en algunos sistemas empresariales Java, ¿cuál es la cantidad adecuada de subprocesos (tamaño del grupo de subprocesos) que se debe planificar?
Permítanme hablar primero de la conclusión: no hay una respuesta fija. Primero establezca expectativas, como cuál espero que sea la utilización de la CPU, cuál es la carga, cuál es la frecuencia del GC y otros indicadores, y luego ajústelos continuamente a un número razonable de subprocesos mediante pruebas.
Por ejemplo, para un sistema empresarial normal basado en SpringBoot, el contenedor Tomcat predeterminado + grupo de conexiones HikariCP + reciclador G1, si en este momento el proyecto también necesita un subproceso múltiple (o grupo de subprocesos) para que el escenario empresarial ejecute el negocio procesar de forma asincrónica/en paralelo.
En este momento, si planifico la cantidad de subprocesos de acuerdo con la fórmula anterior, el error será muy grande. Debido a que ya hay muchos subprocesos en ejecución en este host en este momento, Tomcat tiene su propio grupo de subprocesos, HikariCP también tiene su propio subproceso en segundo plano, JVM también tiene algunos subprocesos compilados e incluso G1 tiene su propio subproceso en segundo plano. Estos subprocesos también se ejecutan en el proceso actual y en el host actual, y también ocuparán recursos de la CPU.
Por lo tanto, debido a la interferencia ambiental, es difícil planificar con precisión la cantidad de subprocesos basándose únicamente en fórmulas y debe verificarse mediante pruebas.
El proceso generalmente es el siguiente:
1. Analice si hay interferencia de otros procesos en el host actual
2. Analice si hay otros subprocesos en ejecución o posibles en ejecución en el proceso JVM actual
3. Establecer metas
    • 目标CPU利用率 - 我最高能容忍我的CPU飙到多少?
    • 目标GC频率/暂停时间 - 多线程执行后,GC频率会增高,最大能容忍到什么频率,每次暂停时间多少?
    • 执行效率 - 比如批处理时,我单位时间内要开多少线程才能及时处理完毕
    • ……
4. 梳理链路关键点,是否有卡脖子的点,因为如果线程数过多,链路上某些节点资源有限可能会导致大量的线程在等待资源(比如三方接口限流,连接池数量有限,中间件压力过大无法支撑等)
5.不断的增加/减少线程数来测试,按最高的要求去测试,最终获得一个“满足要求”的线程数
而且而且而且!不同场景下的线程数理念也有所不同:
  1. Tomcat中的maxThreads,在Blocking I/O和No-Blocking I/O下就不一样
  2. Dubbo 默认还是单连接呢,也有I/O线程(池)和业务线程(池)的区分,I/O线程一般不是瓶颈,所以不必太多,但业务线程很容易称为瓶颈
  3. Redis 6.0以 后也是多线程了,不过它只是I/O 多线程,“业务”处理还是单线程
所以,不要纠结设置多少线程了。没有标准答案,一定要结合场景,带着目标,通过测试去找到一个最合适的线程数。
可能还有同学可能会有疑问:“我们系统也没啥压力,不需要那么合适的线程数,只是一个简单的异步场景,不影响系统其他功能就可以”
很正常,很多的内部业务系统,并不需要啥性能,稳定好用符合需求就可以了。那么我的推荐的线程数是:CPU核心数。

附录

Java 获取CPU核心数

  
  
  
  
  
Runtime.getRuntime().availableProcessors()//获取逻辑核心数,如6核心12线程,那么返回的是12

Linux 获取CPU核心数

  
  
  
  
  
# 总核数 = 物理CPU个数 X 每颗物理CPU的核数 # 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数
# 查看物理CPU个数cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
# 查看每个物理CPU中core的个数(即核数)cat /proc/cpuinfo| grep "cpu cores"| uniq
# 查看逻辑CPU的个数cat /proc/cpuinfo| grep "processor"| wc -l
-end-

本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

开源框架 NanUI 作者转行卖钢材,项目暂停开发 苹果 App Store 免费榜第一是黄色软件 TypeScript 刚刚流行起来,为什么大牛们就开始抛弃了? TIOBE 10 月榜单:Java 跌幅最大,C# 逼近 Java Rust 1.73.0 发布 男子受 AI 女友怂恿刺杀英国女王,被判入狱九年 Qt 6.6 正式发布 路透社:RISC-V 技术成为中美科技战的新战场 RISC-V:不受任何单一公司或国家的控制 联想计划推出 Android PC
{{o.name}}
{{m.name}}

Supongo que te gusta

Origin my.oschina.net/u/4090830/blog/10117401
Recomendado
Clasificación