tutorial thread and python multithreading python-3.x multiprocessing

and - python pool thread



¿En qué núcleos de CPU se ejecutan mis procesos de Python? (3)

P: ¿Es verdad que un intérprete de Python usa solo un núcleo de CPU a la vez para ejecutar todos los subprocesos?

No. La afinidad GIL y CPU no son conceptos relacionados. GIL puede liberarse durante el bloqueo de las operaciones de E / S, cálculos intensivos de CPU largos dentro de una extensión C de todos modos.

Si un hilo está bloqueado en GIL; probablemente no se encuentre en ningún núcleo de CPU y, por lo tanto, es justo decir que el código de multithreading puro de Python puede usar solo un núcleo de CPU a la vez en la implementación de CPython.

P: En otras palabras, ¿la sesión de intérprete de Python 1 (de la figura) ejecutará los 3 hilos (Main_thread, TCP_thread y UDP_thread) en un núcleo de CPU?

No creo que CPython maneje la afinidad de la CPU implícitamente. Es probable que dependa del programador del sistema operativo para elegir dónde ejecutar un hilo. Los hilos de Python se implementan sobre los hilos del sistema operativo real.

P: ¿ O el intérprete de Python puede extenderlos por múltiples núcleos?

Para saber la cantidad de CPU utilizables:

>>> import os >>> len(os.sched_getaffinity(0)) 16

Nuevamente, si los hilos están programados en diferentes CPU no depende del intérprete de Python.

P: Supongamos que la respuesta a la Pregunta 1 es ''núcleos múltiples'', ¿tengo alguna manera de rastrear en qué núcleo se está ejecutando cada subproceso, quizás con algunas declaraciones impresas esporádicas? Si la respuesta a la Pregunta 1 es ''solo un núcleo'', ¿tengo alguna forma de saber cuál es?

Imagino que una CPU específica puede cambiar de una ranura de tiempo a otra. Podría ver algo como /proc/<pid>/task/<tid>/status en kernels de Linux antiguos . En mi máquina, task_cpu se puede leer de /proc/<pid>/stat o /proc/<pid>/task/<tid>/stat :

>>> open("/proc/{pid}/stat".format(pid=os.getpid()), ''rb'').read().split()[-14] ''4''

Para una solución portátil actual, vea si psutil expone dicha información.

Puede restringir el proceso actual a un conjunto de CPU:

os.sched_setaffinity(0, {0}) # current process on 0-th core

P: Para esta pregunta, nos olvidamos de los hilos, pero nos centramos en el mecanismo del subproceso en Python. Iniciar un nuevo subproceso implica iniciar una nueva sesión / shell de intérprete de Python. ¿Es esto correcto?

Sí. subprocess módulo de subprocess crea nuevos procesos de sistema operativo. Si ejecuta el ejecutable de python entonces se inicia un nuevo interperador de Python. Si ejecuta un script bash entonces no se crea un nuevo intérprete de Python, es decir, ejecutar bash ejecutable no inicia un nuevo intérprete / sesión de Python / etc.

P: Suponiendo que sea correcto, ¿Python será lo suficientemente inteligente como para hacer que esa sesión de intérprete se ejecute en un núcleo de CPU diferente? ¿Hay alguna manera de rastrear esto, tal vez con algunas declaraciones impresas esporádicas también?

Consulte más arriba (es decir, el SO decide dónde ejecutar el hilo y podría haber una API del sistema operativo que exponga dónde se ejecuta el hilo).

multiprocessing.Process(target=foo, args=(q,)).start()

multiprocessing.Process también crea un nuevo proceso de sistema operativo (que ejecuta un nuevo intérprete de Python).

En realidad, mi subproceso es otro archivo. Entonces este ejemplo no funcionará para mí.

Python usa módulos para organizar el código. Si su código está en another_file.py entonces import another_file en su módulo principal y pase another_file.foo a multiprocessing.Process .

Sin embargo, ¿cómo lo compararía con p = subprocess.Popen (..)? ¿Importa si comienzo el nuevo proceso (o debería decir ''instancia de intérprete de Python'') con subproceso.Popen (..) frente a multiprocesamiento.Proceso (..)?

multiprocessing.Process() probablemente se implemente en la parte superior del subprocess.Popen() . multiprocessing proporciona una API que es similar a la API de threading y abstrae los detalles de la comunicación entre los procesos de Python (cómo se serializan los objetos de Python para enviarlos entre procesos).

Si no hay tareas intensivas de la CPU, entonces podría ejecutar su GUI y subprocesos de E / S en un solo proceso. Si tienes una serie de tareas intensivas de CPU y luego utilizas múltiples CPU a la vez, utiliza múltiples hilos con extensiones C como lxml , regex , numpy (o tu propia creada con Cython ) que pueden liberar GIL durante cálculos largos o descargarlos en procesos separados (una forma simple es usar un grupo de procesos como el proporcionado por concurrent.futures ).

P: La discusión comunitaria planteó una nueva pregunta. Aparentemente hay dos enfoques cuando se genera un nuevo proceso (dentro de una nueva instancia de intérprete de Python):

# Approach 1(a) p = subprocess.Popen([''python'', mySubprocessPath], shell = True) # Approach 1(b) (J.F. Sebastian) p = subprocess.Popen([sys.executable, mySubprocessPath]) # Approach 2 p = multiprocessing.Process(target=foo, args=(q,))

"Enfoque 1 (a)" es incorrecto en POSIX (aunque puede funcionar en Windows). Para la portabilidad, use "Método 1 (b)" a menos que sepa que necesita cmd.exe (pase una cadena en este caso, para asegurarse de que se usa el escape de línea de comando correcto).

El segundo enfoque tiene la desventaja obvia de que se dirige solo a una función, mientras que necesito abrir un nuevo script de Python. De todos modos, ¿ambos enfoques son similares en lo que logran?

subprocess crea nuevos procesos, cualquier proceso, por ejemplo, puede ejecutar un script bash. multprocessing se usa para ejecutar código Python en otro proceso. Es más flexible importar un módulo de Python y ejecutar su función que ejecutarlo como un script. Consulte la secuencia de comandos Call python con input en un script de python mediante subproceso .

La puesta en marcha

He escrito una pieza de software bastante compleja en Python (en una PC con Windows). Mi software comienza básicamente con dos intérpretes de Python. El primer shell se inicia (supongo) cuando haces doble clic en el archivo main.py Dentro de ese shell, otros hilos se inician de la siguiente manera:

# Start TCP_thread TCP_thread = threading.Thread(name = ''TCP_loop'', target = TCP_loop, args = (TCPsock,)) TCP_thread.start() # Start UDP_thread UDP_thread = threading.Thread(name = ''UDP_loop'', target = UDP_loop, args = (UDPsock,)) TCP_thread.start()

Main_thread inicia un TCP_thread y un UDP_thread . Aunque se trata de hilos separados, todos se ejecutan dentro de un único shell de Python.

Main_thread también inicia un subproceso. Esto se hace de la siguiente manera:

p = subprocess.Popen([''python'', mySubprocessPath], shell=True)

De la documentación de Python, entiendo que este subproceso se ejecuta simultáneamente (!) En una sesión / shell de intérprete de Python por separado. El Main_thread en este subproceso está completamente dedicado a mi GUI. La GUI inicia un TCP_thread para todas sus comunicaciones.

Sé que las cosas se complican un poco. Por lo tanto, he resumido toda la configuración en esta figura:

Tengo varias preguntas con respecto a esta configuración. Los enumeraré aquí:

Pregunta 1 [ Resuelto ]

¿Es verdad que un intérprete de Python usa solo un núcleo de CPU a la vez para ejecutar todos los hilos? En otras palabras, ¿la Python interpreter session 1 (de la figura) ejecutará los 3 hilos ( Main_thread , TCP_thread y UDP_thread ) en un núcleo de CPU?

Respuesta: sí, esto es cierto. El GIL (Global Interpreter Lock) garantiza que todos los hilos se ejecutan en un núcleo de CPU a la vez.

Pregunta 2 [ Aún no resuelto ]

¿Tengo una forma de rastrear qué núcleo de CPU es?

Pregunta 3 [ En parte resuelto ]

Para esta pregunta, nos olvidamos de los hilos , pero nos centramos en el mecanismo del subproceso en Python. Iniciar un nuevo subproceso implica iniciar una nueva instancia de intérprete de Python. ¿Es esto correcto?

Respuesta: Sí, esto es correcto. Al principio hubo cierta confusión sobre si el siguiente código crearía una nueva instancia de intérprete de Python:

p = subprocess.Popen([''python'', mySubprocessPath], shell = True)

El problema ha sido aclarado. Este código de hecho inicia una nueva instancia de intérprete de Python.

¿Será Python lo suficientemente inteligente como para hacer que esa instancia de intérprete de Python separada se ejecute en un núcleo de CPU diferente? ¿Hay alguna manera de rastrear cuál, quizás con algunas declaraciones impresas esporádicas también?

Pregunta 4 [ Nueva pregunta ]

La discusión comunitaria planteó una nueva pregunta. Aparentemente hay dos enfoques cuando se genera un nuevo proceso (dentro de una nueva instancia de intérprete de Python):

# Approach 1(a) p = subprocess.Popen([''python'', mySubprocessPath], shell = True) # Approach 1(b) (J.F. Sebastian) p = subprocess.Popen([sys.executable, mySubprocessPath]) # Approach 2 p = multiprocessing.Process(target=foo, args=(q,))

El segundo enfoque tiene la desventaja obvia de que se dirige solo a una función, mientras que necesito abrir un nuevo script de Python. De todos modos, ¿ambos enfoques son similares en lo que logran?


1, 2: Tienes tres hilos reales, pero en CPython están limitados por GIL, por lo que, suponiendo que ejecuten código python puro, verás el uso de la CPU como si solo se utilizara un núcleo.

3: Como dijo gdlmx, corresponde al sistema operativo elegir un núcleo para ejecutar un hilo, pero si realmente necesita control, puede establecer el proceso o enhebrar la afinidad usando API nativa a través de ctypes . Como estás en Windows, sería así:

# This will run your subprocess on core#0 only p = subprocess.Popen([''python'', mySubprocessPath], shell = True) cpu_mask = 1 ctypes.windll.kernel32.SetProcessAffinityMask(p._handle, cpu_mask)

Yo uso aquí privado Popen._handle para simplicidad. La manera limpia sería OpenProcess(p.tid) etc.

Y sí, el subprocess ejecuta python como todo lo demás en otro proceso nuevo.


Dado que está utilizando el módulo de threading que se acumula en el thread . Tal como lo sugiere la documentación, utiliza la implementación '''' POSIX thread implementation '''' de su sistema operativo.

  1. Los hilos son gestionados por el sistema operativo en lugar del intérprete de Python. Entonces, la respuesta dependerá de la biblioteca pthread en su sistema. Sin embargo, CPython usa GIL para evitar que varios subprocesos ejecuten códigos de bytes de Python de forma simultánea. Entonces serán secuencializados. Pero aún así pueden separarse en diferentes núcleos, lo que depende de tus pthread libs.
  2. Simplemente use un depurador y adjúntelo a su python.exe. Por ejemplo, el comando de hilo GDB .
  3. Similar a la pregunta 1, el nuevo proceso es administrado por su sistema operativo y probablemente se ejecuta en un núcleo diferente. Use depurador o cualquier monitor de proceso para verlo. Para obtener más detalles, vaya a la page documentación de CreatProcess() .