15 - Ajuste de subprocesos múltiples (Parte 1): ¿Qué operaciones provocan cambios de contexto?

1. Primera introducción al cambio de contexto

Primero debemos entender qué es el cambio de contexto.

De hecho, en la era de un solo procesador, el sistema operativo podría manejar tareas simultáneas de múltiples subprocesos. El procesador asigna un intervalo de tiempo de CPU (Time Slice) a cada subproceso, y el subproceso ejecuta tareas dentro del intervalo de tiempo asignado.

El intervalo de tiempo de la CPU es el período de tiempo asignado por la CPU a cada subproceso para su ejecución, generalmente decenas de milisegundos. No podemos sentir los hilos cambiando entre sí en un período de tiempo tan corto, por lo que parece que están sucediendo al mismo tiempo.

El intervalo de tiempo determina cuánto tiempo un subproceso puede ocupar continuamente el procesador. Cuando el intervalo de tiempo de un subproceso se agota o se ve obligado a pausar por sus propios motivos, en este momento, el sistema operativo seleccionará otro subproceso (que puede ser el mismo subproceso o un subproceso de otro proceso) para ocupar el procesador. . Este proceso en el que un hilo se suspende y se le priva del derecho de uso, y se selecciona otro hilo para iniciar o continuar ejecutándose se llama cambio de contexto.

En concreto, cuando a un hilo se le priva del derecho a utilizar el procesador y se suspende, se "corta"; cuando se selecciona un hilo para ocupar el procesador para iniciar o continuar ejecutándose, se "corta". En este proceso de entrada y salida, el sistema operativo necesita guardar y restaurar la información de progreso correspondiente, que es el "contexto".

Entonces, ¿qué incluye el contexto? Específicamente, incluye el contenido de almacenamiento de los registros y el contenido de las instrucciones almacenado en el contador de programa. Los registros de la CPU son responsables de almacenar las tareas que se han ejecutado, se están ejecutando y se ejecutarán, y el contador del programa es responsable de almacenar la ubicación de la instrucción que ejecuta la CPU y la ubicación de la siguiente instrucción que se ejecutará.

En la situación actual donde el número de CPU es mucho mayor que uno, el sistema operativo asigna CPU a tareas de subprocesos por turnos. En este momento, el cambio de contexto se vuelve más frecuente y hay cambio de contexto entre CPU. En comparación con un solo núcleo. cambio de contexto, el cambio entre núcleos es más caro.

2. Incentivos de cambio de contexto multiproceso

En el sistema operativo, los tipos de cambio de contexto también se pueden dividir en cambio de contexto entre procesos y cambio de contexto entre subprocesos. En la programación de subprocesos múltiples, lo que enfrentamos principalmente es el problema de rendimiento causado por el cambio de contexto entre subprocesos. Centrémonos en las causas del cambio de contexto de subprocesos múltiples. Antes de comenzar, echemos un vistazo al estado del ciclo de vida de los subprocesos de Java.

En el diagrama, podemos ver que los subprocesos tienen principalmente cinco estados: "NUEVO", "Listo" (EJECUTABLE), "EN EJECUCIÓN", "BLOQUEADO" y "MUERTO".

Durante este proceso en ejecución, el proceso de convertir un subproceso de RUNNABLE a no RUNNABLE es un cambio de contexto de subproceso.

El estado de un hilo cambia de EJECUTANDO a BLOQUEADO, luego de BLOQUEADO a EJECUTABLE y luego el programador lo selecciona para su ejecución.Este es un proceso de cambio de contexto.

Cuando un hilo cambia del estado EN EJECUCIÓN al estado BLOQUEADO, lo llamamos pausa del hilo. Después de cortar la pausa del hilo, el sistema operativo guardará el contexto correspondiente para que el hilo pueda ejecutar el progreso anterior cuando ingrese al RUNNABLE. estado nuevamente más tarde.Continuar la ejecución sobre la base de.

Cuando un subproceso ingresa al estado EJECUTABLE desde el estado BLOQUEADO, lo llamamos el despertar de un subproceso. En este momento, el subproceso obtendrá el último contexto guardado y continuará completando la ejecución.

A través del estado de ejecución de los subprocesos y el cambio mutuo entre estados, podemos entender que el cambio de contexto de subprocesos múltiples en realidad es causado por el cambio mutuo de dos estados de ejecución de subprocesos múltiples.

Entonces, cuando el hilo se está ejecutando, el estado del hilo cambia de EN EJECUCIÓN a BLOQUEADO o de BLOQUEADO a EJECUTABLE. ¿Qué causa esto?

Podemos analizarlo en dos situaciones, una es un cambio desencadenado por el propio programa, al que llamamos cambio de contexto espontáneo, y la otra es un cambio de contexto no espontáneo inducido por el sistema o máquina virtual.

El cambio de contexto espontáneo significa que un hilo se corta debido a una llamada desde un programa Java. En la programación de subprocesos múltiples, llamar a los siguientes métodos o palabras clave a menudo desencadena un cambio de contexto espontáneo.

  • dormir()
  • esperar()
  • producir()
  • unirse()
  • parque()
  • sincronizado
  • cerrar

El cambio de contexto no espontáneo significa que el hilo se ve obligado a salir por motivos del programador. Los más comunes incluyen: el intervalo de tiempo asignado al subproceso se agota, la recolección de basura de la máquina virtual o problemas de prioridad de ejecución.

El enfoque aquí es "Por qué la recolección de basura de máquinas virtuales provoca un cambio de contexto". En la máquina virtual Java, la memoria de los objetos se asigna mediante el montón en la máquina virtual. Durante la ejecución del programa, se crearán continuamente nuevos objetos. Si los objetos antiguos no se reciclan después de su uso, la memoria del montón se agotará rápidamente. usado, agotado. La máquina virtual Java proporciona un mecanismo de reciclaje para reciclar objetos que ya no se utilizan después de su creación, garantizando así una asignación sostenible de memoria del montón. El uso de este mecanismo de recolección de basura puede provocar que se produzca el evento de detención del mundo, que en realidad es un comportamiento de suspensión de subprocesos.

3. Descubra el cambio de contexto

Siempre decimos que el cambio de contexto provocará una sobrecarga del sistema, pero ¿son realmente tan graves los problemas de rendimiento que conlleva? ¿Cómo detectamos cambios de contexto? ¿Cuál es exactamente el costo del cambio de contexto? A continuación, daré un fragmento de código para comparar la velocidad de ejecución en serie y ejecución concurrente, y luego responderé estas preguntas una por una.

public class DemoApplication {
       public static void main(String[] args) {
              // 运行多线程
              MultiThreadTester test1 = new MultiThreadTester();
              test1.Start();
              // 运行单线程
              SerialTester test2 = new SerialTester();
              test2.Start();
       }
       
       
       static class MultiThreadTester extends ThreadContextSwitchTester {
              @Override
              public void Start() {
                     long start = System.currentTimeMillis();
                     MyRunnable myRunnable1 = new MyRunnable();
                     Thread[] threads = new Thread[4];
                     // 创建多个线程
                     for (int i = 0; i < 4; i++) {
                           threads[i] = new Thread(myRunnable1);
                           threads[i].start();
                     }
                     for (int i = 0; i < 4; i++) {
                           try {
                                  // 等待一起运行完
                                  threads[i].join();
                           } catch (InterruptedException e) {
                                  // TODO Auto-generated catch block
                                  e.printStackTrace();
                           }
                     }
                     long end = System.currentTimeMillis();
                     System.out.println("multi thread exce time: " + (end - start) + "s");
                     System.out.println("counter: " + counter);
              }
              // 创建一个实现 Runnable 的类
              class MyRunnable implements Runnable {
                     public void run() {
                           while (counter < 100000000) {
                                  synchronized (this) {
                                         if(counter < 100000000) {
                                                increaseCounter();
                                         }
                                         
                                  }
                           }
                     }
              }
       }
       
      // 创建一个单线程
       static class SerialTester extends ThreadContextSwitchTester{
              @Override
              public void Start() {
                     long start = System.currentTimeMillis();
                     for (long i = 0; i < count; i++) {
                           increaseCounter();
                     }
                     long end = System.currentTimeMillis();
                     System.out.println("serial exec time: " + (end - start) + "s");
                     System.out.println("counter: " + counter);
              }
       }
 
       // 父类
       static abstract class ThreadContextSwitchTester {
              public static final int count = 100000000;
              public volatile int counter = 0;
              public int getCount() {
                     return this.counter;
              }
              public void increaseCounter() {
                     
                     this.counter += 1;
              }
              public abstract void Start();
       }
}

Después de la ejecución, observe los resultados de las pruebas de tiempo de los dos:

A través de la comparación de datos, podemos ver que la velocidad de ejecución en serie es más rápida que la velocidad de ejecución concurrente. Esto se debe a que el cambio de contexto de los subprocesos provoca una sobrecarga adicional. El uso de la palabra clave de bloqueo sincronizado conduce a la competencia de recursos, lo que provoca el cambio de contexto. Sin embargo, incluso si no se utiliza la palabra clave de bloqueo sincronizado, la velocidad de ejecución concurrente no puede exceder la velocidad de ejecución en serie. , esto se debe a que el cambio de contexto también existe en subprocesos múltiples. El diseño de Redis y NodeJS refleja bien las ventajas de la serialización de un solo subproceso.

En el sistema Linux, puede utilizar el comando vmstat proporcionado por el kernel de Linux para monitorear la frecuencia de cambio de contexto del sistema durante la ejecución del programa Java. cs se muestra en la siguiente figura:

Si está monitoreando el cambio de contexto de una aplicación, puede usar el comando pidstat para monitorear el cambio de contexto del proceso especificado.

Dado que Windows no tiene una herramienta como vmstat, en Windows, podemos usar Process Explorer para ver la cantidad de cambios de contexto entre subprocesos cuando se ejecuta el programa.

En cuanto a los enlaces específicos en el proceso de conmutación donde se produce la sobrecarga del sistema, el resumen es el siguiente:

  • El sistema operativo guarda y restaura el contexto;
  • El planificador realiza la programación de subprocesos;
  • Recarga de caché del procesador;
  • El cambio de contexto también puede hacer que se vacíe toda el área de la caché, lo que genera una sobrecarga de tiempo.

4. Resumen

El cambio de contexto es el proceso en el que otro subproceso suspende un subproceso de trabajo y el otro subproceso ocupa el procesador y comienza a ejecutar la tarea. Las operaciones de llamada espontáneas y no espontáneas del sistema y los programas Java provocarán un cambio de contexto, lo que provocará una sobrecarga del sistema.

Cuantos más subprocesos haya, más rápido se ejecutará el sistema, no necesariamente. Entonces, ¿cuándo usamos generalmente un solo subproceso y cuándo usamos múltiples subprocesos cuando la cantidad de concurrencia es relativamente grande?

Generalmente, podemos usar un solo subproceso cuando una única lógica es relativamente simple y la velocidad es relativamente muy rápida. Por ejemplo, Redis del que hablamos anteriormente puede leer rápidamente valores de la memoria sin considerar el problema de bloqueo causado por los cuellos de botella de E/S. En escenarios donde la lógica es relativamente compleja, el tiempo de espera es relativamente largo o se requieren una gran cantidad de cálculos, recomiendo utilizar subprocesos múltiples para mejorar el rendimiento general del sistema. Por ejemplo, operaciones de lectura y escritura de archivos, procesamiento de imágenes y análisis de big data en la era NIO.

5. Preguntas para pensar

Lo que discutimos principalmente anteriormente es el cambio de contexto de subprocesos múltiples. Cuando hablé de clasificación anteriormente, también mencioné el cambio de contexto entre procesos. Entonces, ¿sabías que se producirá un cambio de contexto entre procesos cuando se utiliza Sincronizado en subprocesos múltiples? ¿En qué áreas específicas sucederá?

Supongo que te gusta

Origin blog.csdn.net/qq_34272760/article/details/132734245
Recomendado
Clasificación