Red de conocimiento de abogados - Derecho de sociedades - Cómo entender la "Programación de servidores multiproceso Linux"

Cómo entender la "Programación de servidores multiproceso Linux"

1: Procesos e hilos

Cada proceso tiene su propio espacio de direcciones independiente. "En el mismo proceso" o "no en el mismo proceso" es un punto de decisión importante en la división de funciones del sistema. "Erlang Programming" [ERL] compara procesos con personas:

Cada uno tiene su propia memoria (memoria) y las personas se comunican a través de una conversación (transmisión de mensajes). La conversación puede ser una entrevista (sobre la misma). computadora) servidor), también puede hablar por teléfono (diferentes servidores, con comunicación de red). La diferencia entre una entrevista y una conversación telefónica es que en una entrevista se puede saber inmediatamente si la otra parte está muerta (caída, SIGCHLD), mientras que en una conversación telefónica sólo se puede juzgar si la otra parte sigue viva mediante latidos periódicos.

Con estas metáforas, el "juego de roles" se puede utilizar al diseñar sistemas distribuidos. Varias personas en el equipo desempeñan cada una un proceso, y el rol de la persona está determinado por el código del proceso (encargado de iniciar sesión). , encargados de distribución de mensajes), encargados de negocios, etc.). Cada uno tiene su propia memoria, pero no conoce la memoria de otras personas. Si desea conocer las opiniones de otras personas, solo puede hablar (sin considerar IPC como memoria compartida por ahora). Entonces puedes pensar en:

·Tolerancia a fallos: en caso de que alguien muera repentinamente

·Expansión: se añaden nuevas personas a mitad de camino

·Balanceo de carga: ten cuidado del trabajo Mueva al niño a la Persona B

·Retiro: si la Persona A quiere corregir el error, no le asigne nuevas tareas primero y reinícielo después de que termine lo que está haciendo

Y otros escenarios, muy convenientes.

La característica de los hilos es que comparten espacio de direcciones, por lo que los datos se pueden compartir de manera eficiente. Varios procesos en una máquina pueden compartir de manera eficiente segmentos de código (que el sistema operativo puede asignar a la misma memoria física), pero no pueden compartir datos. Si varios procesos comparten una gran cantidad de memoria, equivale a escribir programas multiproceso como subprocesos múltiples, lo cual es engañoso.

Creo que el valor del "multiproceso" es utilizar mejor el rendimiento de los procesadores multinúcleo. En la era de un solo núcleo, los subprocesos múltiples no tenían mucho valor (pensamiento personal: si la tarea a completar requiere un uso intensivo de la CPU, entonces los subprocesos múltiples no tienen ninguna ventaja, e incluso los subprocesos múltiples serán más lentos debido a la sobrecarga). de cambio de subprocesos; si la tarea a completar es tanto Si hay computación de CPU como disco o red IO, la ventaja de usar subprocesos múltiples es que cuando un subproceso se bloquea debido a IO, el sistema operativo puede programar la ejecución de otros subprocesos. Aunque la eficiencia es realmente mayor que la ejecución secuencial de tareas, este tipo de tarea puede mejorar la eficiencia a través de un modelo de "multiplexación IO + IO sin bloqueo" de un solo subproceso (basado en eventos). sólo aporta simplicidad en la programación). Alan Cox dijo: "Una computadora es una máquina de estados. Los subprocesos son para personas que no pueden programar máquinas de estados". (Una computadora es una máquina de estados. Los subprocesos son para personas que no pueden programar máquinas de estados). Aunque solo sea una CPU. y una unidad de ejecución, entonces, como dijo Alan Cox, es más eficiente escribir programas basados ​​en la idea de la máquina de estados.

Dos: modelos de programación comunes para servidores de un solo subproceso

Hasta donde yo sé, entre los programas de red de alto rendimiento, el más utilizado es probablemente "IO + sin bloqueo". El modelo IO Multiplexing" es el patrón Reactor.

En el modelo de "multiplexación IO + IO sin bloqueo", la estructura básica del programa es un bucle de eventos, y el negocio se implementa de manera basada en eventos y de devolución de llamada de eventos Lógica:

[cpp] ver copia simple

//El código es solo para ilustración y no considera completamente varias situaciones

while(!done)

{

int timeout_ms = max(1000, getNextTimedCallback());

int retval = poll(fds, nfds, timeout_ms

if (retval); <0){

Manejar errores y devolver la llamada al controlador de errores del usuario

}else{

Manejar temporizadores caducados y devolver la llamada al controlador de temporizador del usuario

if(retval>0){

Procesar eventos de IO y devolver la llamada al controlador de eventos de IO del usuario

}

}

}

Select(2)/poll(2) aquí tiene deficiencias en escalabilidad (cuando hay demasiados descriptores, la eficiencia es baja), puede ser reemplazado por epoll(4) en Linux, y otros También existen alternativas de alto rendimiento al sistema operativo.

Las ventajas del modelo Reactor son obvias. La programación no es difícil y la eficiencia es buena. No solo se puede usar para leer y escribir sockets, establecer conexiones (conectar (2) / aceptar (2)), e incluso la resolución DNS se puede realizar sin bloqueo para mejorar la concurrencia y el rendimiento (rendimiento), para IO -Las aplicaciones intensivas son una buena opción. ligtl/select/encuesta, etc. La diferencia es que TCP es bidireccional y la canalización de Linux es unidireccional. La comunicación bidireccional entre procesos requiere dos descriptores de archivo, lo cual es inconveniente. Además, los procesos deben tener una relación padre-hijo para usar la canalización, lo que limita la conexión. uso de pipe. ;

El puerto TCP está ocupado exclusivamente por un proceso y el sistema operativo reciclará automáticamente el descriptor de archivo cuando el proceso finalice. Por lo tanto, incluso si el programa se cierra inesperadamente, no dejará basura en el sistema. Una vez reiniciado el programa, se puede restaurar fácilmente sin reiniciar el sistema operativo (existe este riesgo cuando se utiliza el mutex entre procesos); El puerto es exclusivo y puede Esto evita que el programa se inicie repetidamente. Si el proceso posterior no puede capturar el puerto, naturalmente no podrá inicializarse, evitando así resultados inesperados;

En comparación con otros IPC, uno de. Los beneficios inherentes del protocolo TCP es que "Grabable y reproducible". tcpdump y Wireshark son buenos ayudantes para resolver disputas de protocolo y estado entre dos procesos, y también son herramientas poderosas para el análisis de rendimiento (rendimiento, latencia). Podemos usar esto para escribir pruebas de regresión automatizadas para programas distribuidos. También puede utilizar herramientas como tcpcopy para realizar pruebas de estrés. TCP también puede ser multilingüe y el servidor y el cliente no tienen que utilizar el mismo idioma.

El diseño del software y la división funcional de los sistemas distribuidos generalmente deben basarse en "procesos". Desde una perspectiva macro, un sistema distribuido se compone de múltiples procesos que se ejecutan en múltiples máquinas y se utilizan conexiones largas TCP para comunicarse entre procesos.

Hay dos ventajas al utilizar conexiones TCP largas: primero, es fácil localizar las dependencias entre servicios en el sistema distribuido. Simplemente ejecute netstat -tpna|grep en la máquina para enumerar inmediatamente las direcciones de los clientes (columna Dirección extranjera) que utilizan un determinado servicio y luego use el comando netstat o lsof en la máquina cliente para averiguar qué proceso inició la conexión. . Las conexiones cortas TCP y UDP no tienen esta característica. En segundo lugar, es más fácil localizar fallos de red o de programa a lo largo de las colas de recepción y envío.

Durante el funcionamiento normal, tanto Recv-Q como Send-Q impresos por netstat deben estar cerca de 0 o oscilar alrededor de 0. Si Recv-Q permanece sin cambios o continúa aumentando, generalmente significa que el proceso de servicio se está desacelerando y es posible que se haya producido un punto muerto o bloqueo. Si Send-Q permanece sin cambios o continúa aumentando, es posible que el servidor de la otra parte esté demasiado ocupado y no tenga tiempo para procesarlo. También puede ser que un enrutador o conmutador en el medio de la red esté defectuoso, lo que provoca que se produzcan paquetes. pérdida, o incluso el servidor de la otra parte está fuera de línea. Estos factores pueden aparecer como datos que no se pueden enviar. El monitoreo continuo de Recv-Q y Send-Q puede proporcionar una advertencia temprana sobre fallas de rendimiento o disponibilidad. El siguiente es un ejemplo de bloqueo de subprocesos del lado del servidor que provoca un aumento en Recv-Q y Send-Q del cliente:

[cpp] ver copia simple

$netstat -tn

Proto Recv-Q Send-Q Dirección local Extranjero

tcp 78393 0 10.0.0.10:2000 10.0.0.10:39748 #Conexión al servidor

tcp 0 132608 10.0. 0.10:39748 10.0.0.10:2000 #Conexión de cliente

tcp 0 52 10.0.0.10:22 10.0.0.4:55572

Cinco: ocasiones aplicables para servidores multiproceso

Si desea proporcionar un servicio o realizar una tarea en una máquina multinúcleo, los modos disponibles son:

a: ejecutar un proceso de un solo subproceso;

b: ejecutar un proceso multiproceso;

c: ejecutar múltiples procesos de un solo subproceso;

d: ejecutar múltiples procesos multiproceso;

Considere esto Escenario: si se utiliza una biblioteca de compresión de datos con una velocidad de 50 MB/s, el costo de creación y destrucción del proceso es de 800 microsegundos y el costo de creación y destrucción de subprocesos es de 50 microsegundos. ¿Cómo realizar tareas de compresión?

Si desea comprimir ocasionalmente un archivo de texto de 1 GB y el tiempo de ejecución estimado es de 20 segundos, entonces es razonable iniciar un proceso para hacerlo, porque el costo de iniciar y destruir el proceso es mucho menor. que el tiempo real de la tarea.

Si desea comprimir datos de texto de 500 kB con frecuencia y el tiempo de ejecución estimado es de 10 ms, iniciar el proceso cada vez parece un poco inútil. Puede iniciar un hilo separado cada vez para hacerlo.

Si desea comprimir datos de texto de 10 kB con frecuencia y el tiempo de ejecución estimado es de 200 microsegundos, iniciar el hilo cada vez parece un desperdicio. Es mejor hacerlo directamente en el hilo actual. También puede usar un grupo de subprocesos y entregar la tarea de compresión al grupo de subprocesos cada vez para evitar bloquear el subproceso actual (especialmente evitar bloquear el subproceso IO).

Se puede ver que el subproceso múltiple no es una solución milagrosa.

1: Ocasiones donde se debe usar un solo hilo

Hasta donde yo sé, hay dos ocasiones donde se debe usar un solo hilo:

a : El programa puede bifurcarse (2);

En la programación real, se debe garantizar que solo los programas de un solo subproceso puedan realizar la bifurcación(2). No es que los programas multiproceso no puedan llamar a fork(2), pero hacerlo encontrará muchos problemas:

Por lo general, no se puede llamar a fork en programas multiproceso, porque el fork de Linux solo clona el hilo de control del hilo actual, no se pueden clonar otros hilos. Después de la bifurcación, todos los hilos excepto el hilo actual desaparecen.

Esto crea una situación peligrosa. Otro hilo puede estar justo dentro de la sección crítica, sosteniendo cierto candado, y de repente muere sin siquiera tener la oportunidad de desbloquearlo. En este momento, si el proceso hijo intenta bloquear el mismo mutex nuevamente, se bloqueará inmediatamente.

Por lo tanto, después de la bifurcación, el proceso hijo equivale a estar en el controlador de señales (porque no sabemos qué función está llamando el hilo en el proceso padre cuando se llama a la bifurcación, que es lo mismo que cuando ocurre la señal), y no se puede llamar a la función de seguridad de subprocesos (a menos que sea reentrante) y solo se pueden llamar funciones asincrónicas seguras para señales. Por ejemplo, después de la bifurcación, el proceso hijo no puede llamar a:

malloc, porque es casi seguro que malloc se bloqueará al acceder al estado global

Cualquier función que pueda asignar o liberar memoria, como por ejemplo; como snprintf ;

Cualquier función Pthreads;

funciones de la serie printf, porque es posible que otros subprocesos contengan bloqueos stdout/stderr;

Excepto aquellos enumerados explícitamente en man 7 señal fuera de cualquier función que no sea la función segura de señal.

Por lo tanto, la única forma segura de llamar a fork en múltiples subprocesos es llamar inmediatamente a exec para ejecutar otro programa después de fork, cortando por completo la conexión entre el proceso hijo y el proceso padre.

Después de llamar a fork en un entorno multiproceso, se genera un proceso hijo. Solo hay un hilo dentro del proceso hijo, que es una copia del hilo que llamó a la bifurcación en el proceso padre.

Cuando usas fork para crear un proceso hijo, el proceso hijo también hereda el estado de todos los mutex, bloqueos de lectura y escritura y variables de condición del proceso padre al heredar una copia de todo el espacio de direcciones. Si un subproceso en el proceso padre contiene bloqueos, el proceso hijo también los mantiene. El problema es que el proceso hijo no contiene una copia del hilo que contiene el bloqueo, por lo que el proceso hijo no tiene forma de saber qué bloqueos tiene y cuáles necesita liberar.

Aunque Pthread proporciona la función pthread_atfork para intentar evitar este tipo de problemas, esto hace que el código sea confuso. Por lo tanto, el autor del libro "Programación con subprocesos Posix" dijo: "Evite el uso de bifurcaciones en el código de subprocesos, excepto cuando el proceso hijo ejecutará inmediatamente un nuevo programa".

b: limita el uso de CPU del programa.

Esto es fácil de entender, por ejemplo, en un servidor de 8 núcleos, incluso si un programa de un solo subproceso está ocupado. ocurre, ocupará 1 por núcleo, su uso de CPU es solo del 12,5%. En el peor de los casos, el sistema todavía tiene el 87,5% de los recursos informáticos disponibles para otros procesos de servicio.

Por lo tanto, para algunos programas auxiliares, si deben ejecutarse en la misma máquina que el proceso de servicio principal, convertirlos en un solo subproceso puede evitar acaparar excesivamente los recursos informáticos del sistema.