Python Language
Concurrencia de Python
Buscar..
Observaciones
Los desarrolladores de Python se aseguraron de que la API entre threading
y multiprocessing
sea similar, de modo que el cambio entre las dos variantes sea más fácil para los programadores.
El módulo de enhebrado.
from __future__ import print_function
import threading
def counter(count):
while count > 0:
print("Count value", count)
count -= 1
return
t1 = threading.Thread(target=countdown,args=(10,))
t1.start()
t2 = threading.Thread(target=countdown,args=(20,))
t2.start()
En ciertas implementaciones de Python como CPython, cierto paralelismo no se consigue utilizando hilos porque de la utilización de lo que se conoce como el GIL, o G lobal I nterpreter L ock.
Aquí hay una excelente descripción de la concurrencia de Python:
Concordancia Python por David Beazley (YouTube)
El módulo multiprocesamiento.
from __future__ import print_function
import multiprocessing
def countdown(count):
while count > 0:
print("Count value", count)
count -= 1
return
if __name__ == "__main__":
p1 = multiprocessing.Process(target=countdown, args=(10,))
p1.start()
p2 = multiprocessing.Process(target=countdown, args=(20,))
p2.start()
p1.join()
p2.join()
Aquí, cada función se ejecuta en un nuevo proceso. Dado que una nueva instancia de Python VM ejecuta el código, no hay GIL
y se ejecuta el paralelismo en varios núcleos.
El método Process.start
inicia este nuevo proceso y ejecuta la función pasada en el argumento de target
con los argumentos args
. El método Process.join
espera el final de la ejecución de los procesos p1
y p2
.
Los nuevos procesos se inician de manera diferente según la versión de python y la plataforma en la que se ejecuta el código, por ejemplo :
- Windows usa
spawn
para crear el nuevo proceso. - Con los sistemas y versiones de Unix anteriores a 3.3, los procesos se crean utilizando un
fork
.
Tenga en cuenta que este método no respeta el uso POSIX de la bifurcación y, por lo tanto, conduce a comportamientos inesperados, especialmente al interactuar con otras bibliotecas de multiprocesamiento. - Con el sistema Unix y la versión 3.4+, puede elegir iniciar los nuevos procesos con
fork
,forkserver
ospawn
utilizandomultiprocessing.set_start_method
al comienzo de su programa.forkserver
métodosforkserver
yspawn
son más lentos que los forking, pero evitan algunos comportamientos inesperados.
Uso de la horquilla POSIX :
Después de una bifurcación en un programa multiproceso, el niño solo puede llamar de forma segura a funciones async-signal-safe hasta el momento en que lo llame execve.
( ver )
Usando fork, se iniciará un nuevo proceso con el mismo estado exacto para todo el mutex actual, pero solo se MainThread
. Esto no es seguro ya que podría conducir a condiciones de carrera, por ejemplo :
- Si utiliza un
Lock
enMainThread
y lo pasa a otro hilo que se supone que debe bloquearlo en algún momento. Si lafork
ocurre simultáneamente, el nuevo proceso comenzará con un bloqueo bloqueado que nunca se liberará ya que el segundo hilo no existe en este nuevo proceso.
En realidad, este tipo de comportamiento no debería ocurrir en Python puro, ya que el multiprocessing
maneja correctamente, pero si está interactuando con otra biblioteca, puede ocurrir este tipo de comportamiento, lo que puede provocar un fallo en su sistema (por ejemplo, con numpy / acelerado en macOS)
Transferencia de datos entre procesos de multiprocesamiento.
Debido a que los datos son confidenciales cuando se manejan entre dos subprocesos (piense que la lectura concurrente y la escritura concurrente pueden entrar en conflicto entre sí, causando condiciones de carrera), se creó un conjunto de objetos únicos para facilitar la transferencia de datos entre los subprocesos. Cualquier operación verdaderamente atómica puede usarse entre subprocesos, pero siempre es seguro mantener la cola.
import multiprocessing
import queue
my_Queue=multiprocessing.Queue()
#Creates a queue with an undefined maximum size
#this can be dangerous as the queue becomes increasingly large
#it will take a long time to copy data to/from each read/write thread
La mayoría de las personas sugerirán que al usar la cola, siempre coloque los datos de la cola en un intento: excepto: bloque en lugar de usar vacío. Sin embargo, para las aplicaciones en las que no importa si omite un ciclo de exploración (los datos se pueden colocar en la cola mientras se queue.Empty==True
estados de la queue.Empty==True
a la queue.Empty==False
) por lo general es mejor colocar la lectura y acceso de escritura en lo que yo llamo un bloque Iftry, porque una declaración 'if' es técnicamente más eficaz que atrapar la excepción.
import multiprocessing
import queue
'''Import necessary Python standard libraries, multiprocessing for classes and queue for the queue exceptions it provides'''
def Queue_Iftry_Get(get_queue, default=None, use_default=False, func=None, use_func=False):
'''This global method for the Iftry block is provided for it's reuse and
standard functionality, the if also saves on performance as opposed to catching
the exception, which is expencive.
It also allows the user to specify a function for the outgoing data to use,
and a default value to return if the function cannot return the value from the queue'''
if get_queue.empty():
if use_default:
return default
else:
try:
value = get_queue.get_nowait()
except queue.Empty:
if use_default:
return default
else:
if use_func:
return func(value)
else:
return value
def Queue_Iftry_Put(put_queue, value):
'''This global method for the Iftry block is provided because of its reuse
and
standard functionality, the If also saves on performance as opposed to catching
the exception, which is expensive.
Return True if placing value in the queue was successful. Otherwise, false'''
if put_queue.full():
return False
else:
try:
put_queue.put_nowait(value)
except queue.Full:
return False
else:
return True