Cómo terminar correctamente los procesos secundarios con multiprocesamiento en Python

Tengo algunas funciones de callback y me gustaría iniciar como múltiples procesos y hacer que todos terminen a través de la señal del proceso principal.

Mi forma actual de hacer esto es crear un c_bool compartido con multiprocessing.Value Valorar y establecerlo en True , luego distribuirlo a todos mis procesos cuando se crean. Todos mis procesos ejecutan un bucle while utilizando el bool compartido de la siguiente manera:

 while myC_bool: ...keep running... 

Entonces puedo simplemente cambiar el bool a False de mi proceso principal y todos los procesos secundarios completarán su ciclo final y saldrán.

Mucha gente me ha dicho y he leído en los documentos que se debe tratar de evitar el uso de la memoria compartida cuando se utiliza el multiprocesamiento. Me dijeron que la mejor manera de evitar esto es demonizar el proceso, asignarle un controlador de señal personalizado y enviarle un sigint / sigterm / etc …

Mi pregunta es, ¿está utilizando exclusivamente el bool para mantener un bucle vivo y SOLO modifique el valor de mi proceso principal y lo lea en varios procesos secundarios para encontrar una solución adecuada que permita que todos los procesos secundarios finalicen de forma rápida y segura? Siento que hay menos gastos generales para que todos los niños solo miren el bool compartido, en lugar de enviarles x número de sigints.

¿Sería la demonización una mejor solución? Si es así, me gustaría algo de ayuda para entender por qué.

Hay muchas buenas razones para ir con su solución:

  • Es más fácil pensar que señales.
  • Tiene menos problemas multiplataforma para tratar.
  • Ya tienes un código que funciona de esta manera.
  • Facilita agregar un mecanismo de “apagado elegante” si lo desea en el futuro.

… y así.

Tenga en cuenta que, a menos que pueda probarse a sí mismo que el multiprocessing y las primitivas subyacentes del sistema operativo, en todas las plataformas que le interesan, están garantizados para funcionar sin sincronización aquí, debe colocar un Lock u otro elemento en cada acceso al bool compartido. . Eso no es exactamente complicado, pero … una vez que lo haya hecho, usar, por ejemplo, un Event sin el bool compartido puede ser incluso más simple.

En cualquier caso, si alguna de esas razones fuera suya, diría genial, hágalo de esa manera. Pero de acuerdo con su pregunta, realmente eligió esto debido al rendimiento:

Siento que hay menos gastos generales para que todos los niños solo miren el bool compartido, que enviarles x número de sigints.

Si esa es tu razón, casi seguro que estás equivocado. Los niños deben mirar el bool compartido (¡y adquirir el locking compartido!) Cada vez a través de un bucle, mientras que una señal solo debe enviarse a cada niño una vez. Por lo tanto, su sobrecarga seguramente será mucho más alta de esta manera.

Pero, en realidad, no puedo imaginar que la sobrecarga de enviar una señal por proceso hijo, o incluso de obtener un locking entre procesos una vez por bucle por proceso, esté en cualquier lugar cerca de un cuello de botella en cualquier progtwig útil, así que … ¿por qué la sobrecarga importa aquí? ¿en primer lugar? Haz lo que tenga más sentido de la manera más sencilla.

Ya que tienes cuidado de quién modifica la variable compartida, debería estar bien.

Hay muchas soluciones diferentes posibles. Por ejemplo, use un multiprocessing.Event y multiprocessing.Event que los procesos terminen cuando se establece. O usando multiprocessing.Connection Objetos de multiprocessing.Connection (desde Pipe). Este último podría usarse para la comunicación bidireccional entre padres e hijos. Como una señal a los niños para que paren, seguido de una confirmación a los padres.

Las personas que te dicen “no hagas esto” están equivocadas. El punto de la memoria compartida es compartir la memoria entre multiprocesadores y eso es exactamente lo que está haciendo.

Usted tiene una solución que 1) es simple y 2) funciona. El enfoque de señal / demonio es 1) realmente genial y 2) más difícil de codificar correctamente y 3) mucho más difícil de entender.

El único escollo que veo en su enfoque es la posibilidad de que un proceso pueda ver una copia obsoleta del bool de la memoria caché de la CPU, y se demore un poco en el cierre. Hay formas de vaciar la memoria caché para asegurarse de que esto no esté sucediendo, pero es probable que no las necesite porque, en la mayoría de las aplicaciones, la descarga de la memoria caché se realiza con la suficiente frecuencia de forma automática.

Defender su posición.