El uso de multiproceso + corrutina en Python y por qué usarlo
Hablé sobre por qué se recomienda usar multiproceso en lugar de multiproceso en Python, pero el multiproceso también tiene sus propias limitaciones: en comparación con los subprocesos, es más engorroso y lleva más tiempo cambiar. , y en multiproceso de Python, no se recomienda que la cantidad de procesos exceda la cantidad de núcleos de CPU (un proceso tiene solo un GIL, por lo que un proceso solo puede ejecutarse en una CPU), porque cuando un proceso ocupa una CPU, Puede aprovechar al máximo el rendimiento de la máquina, pero si hay demasiados procesos, no vale la pena cambiar de proceso con frecuencia.
Sin embargo, en circunstancias especiales (especialmente tareas intensivas de IO), los subprocesos múltiples son más fáciles de usar que los procesos múltiples.
Por ejemplo: se le proporcionan 2 millones de URL y necesita capturar y guardar la página correspondiente a cada URL. En este caso, el efecto de utilizar varios procesos solo definitivamente será muy pobre. ¿Por qué?
Por ejemplo, el tiempo de espera para cada solicitud es de 2 segundos, luego lo siguiente (ignorando el tiempo de cálculo de la CPU):
1. = 400 W segundos == 1111,11 horas == 46,3 días, esta velocidad es obviamente inaceptable
2. Proceso único + subprocesos múltiples: por ejemplo, abrimos 10 subprocesos múltiples en este proceso, lo cual es mejor que en 1 10 veces la velocidad, es decir, se necesitan aproximadamente 4,63 días para completar 2 millones de rastreos. Tenga en cuenta que la ejecución real aquí es: el subproceso 1 encuentra un bloque, la CPU cambia al subproceso 2 para su ejecución, encuentra el bloque y cambia a. hilo 3, etc., 10 Después de bloquear todos los hilos, el proceso se bloquea y el proceso no puede continuar ejecutándose hasta que se bloquee un determinado hilo, por lo que la velocidad se puede aumentar aproximadamente 10 veces (la sobrecarga causada por el cambio de hilo es ignorado aquí, de hecho La mejora no debería llegar a 10 veces), pero lo que debe tenerse en cuenta es que el cambio de subprocesos también tiene una sobrecarga, por lo que no se pueden iniciar subprocesos múltiples infinitamente (definitivamente no es confiable abrir 2 millones de subprocesos)
3, Multiproceso + multiproceso: esto es excelente aquí. En términos generales, muchas personas usan este método en multiproceso, cada proceso puede ocupar una CPU y el subproceso múltiple evita el bloqueo en espera de un determinado. Hasta cierto punto, es mejor que subprocesos múltiples en un solo proceso. Por ejemplo, si abrimos 10 procesos y abrimos subprocesos de 200 W en cada proceso, la velocidad de ejecución es teóricamente más de 10 veces más rápida que ejecutar subprocesos de 200 W en un solo proceso (. ¿Por qué? Más de 10 veces en lugar de 10 veces. La razón principal es que el consumo de CPU al cambiar subprocesos de 200 W es definitivamente mucho mayor que el de cambiar procesos de 20 W. Teniendo en cuenta este costo, es más de 10 veces.
¿Existe una forma mejor? La respuesta es sí, es:
4. Corrutina, antes de usarla, hablemos de qué/por qué/cómo (qué es/por qué se usa/cómo usarla)
qué:
Coroutine es un hilo ligero a nivel de usuario. Las corrutinas tienen su propio contexto de registro y pila. Cuando se cambia el programa de rutina, el contexto y la pila del registro se guardan en otros lugares. Al volver a cambiar, se restauran el contexto y la pila del registro previamente guardados. Por lo tanto:
La corrutina puede retener el estado de la última llamada (es decir, una combinación específica de todos los estados locales). Cada vez que el proceso vuelve a ingresar, equivale a ingresar al estado de la última vez. llamada En otras palabras: ingrese la posición del flujo lógico donde lo dejó la última vez.
En la programación concurrente, las corrutinas son similares a los subprocesos. Cada corrutina representa una unidad de ejecución, tiene sus propios datos locales y comparte datos globales y otros recursos con otras corrutinas.
Por qué:
Los lenguajes actuales básicamente eligen subprocesos múltiples como una función de concurrencia. El concepto relacionado con subprocesos es multitarea preventiva, mientras que el concepto relacionado con corrutinas es multitarea colaborativa. .
Ya sea un proceso o un subproceso, cada vez que se bloquea o cambia, debe caer en una llamada al sistema. Primero, deje que la CPU ejecute el programador del sistema operativo y luego el programador decida qué proceso. ejecutar (hilo).
Y debido a que no se puede determinar el orden de ejecución de la programación preventiva, los problemas de sincronización deben manejarse con mucho cuidado al usar subprocesos, mientras que las corrutinas no tienen este problema en absoluto (los programas asincrónicos y controlados por eventos también tienen la mismas ventajas).
Debido a que el usuario escribe la lógica de programación para la corrutina, para la CPU, la corrutina es en realidad un solo subproceso, por lo que la CPU no necesita considerar cómo programar o cambiar contextos, lo que elimina la necesidad de Cambio de CPU, por lo que las corrutinas son mejores que los subprocesos múltiples hasta cierto punto.
cómo:
¿Cómo usar corrutinas en Python? La respuesta es usar gevent. Cómo usarlo: consulte aquí
El uso de corrutinas no está limitado por la sobrecarga de subprocesos. Una vez intenté ejecutar 200.000 URL en una corrutina de proceso único y no hubo ningún problema. todo. .
Entonces, el método más recomendado es multiproceso + corrutina (se puede considerar como un solo subproceso en cada proceso, y este único subproceso se corrutina)
En multiproceso + Corrutina, se evita la sobrecarga del cambio de CPU y se pueden utilizar múltiples CPU. Este método puede mejorar en gran medida la eficiencia de los rastreadores con grandes cantidades de datos y lectura y escritura de archivos.
Pequeño ejemplo:
[python]?view Plain?copy
#-*-?coding=utf-8?-*-?
¿importar?solicitudes?
¿desde?multiprocesamiento?¿importar?proceso?
¿importar?gevent?
desde?gevent?import?monkey;? Monkey.patch_all()?
¿importar?sys?
¿recargar(sys)?
sys.setdefaultencoding('utf8')?
def?fetch(url):?
intenta:?
s?=?requests.Session()?
r?=?s. get(url,timeout=1)#¿Capturar la página aquí?
excepto?Exception,e:?
imprimir?e
return?'' ?
def?process_start(url_list):?
tareas?=?[]?
para?url?in?url_list:?
tasks.append(gevent.spawn(fetch,url))?
gevent.joinall(tasks)#¿Usar rutinas para ejecutar?
def?task_start(filepath, flag? =?100000):#¿Iniciar un proceso por cada 100.000 URL?
with?open(filepath,'r')?as?reader:#¿Leer la URL del archivo proporcionado?
url?=?reader.readline().strip()?
url_list?=?[]#¿Esta lista se utiliza para almacenar tareas rutinarias?
i?=?0 ?#Contador, registra cuántas URL se agregan a la cola de rutinas?
while?url!='':?
i?+=?1?
url_list.append(url)#Cada vez que se lee la URL, ¿agregar la URL a la cola?
if?i?==?flag:#Comenzar con una cierta cantidad de URL Un proceso y ¿ejecutar?
p?=?Process(target=process_start,args=(url_list,))?
p.start()?
url_list?= ?[]?#¿Restablecer cola de URL?
i?=?0?#¿Restablecer contador?
url?=?reader.readline().strip( )?
if?url_list?not?[]: #¿Qué pasa si todavía quedan URL en la cola de tareas después de salir del bucle?
p?=?Process(target=process_start,args= ( url_list,))#¿Poner todas las URL restantes en el último proceso para su ejecución?
p.start()?
if?__name__?==?'__main__': ? p>
task_start('./testData.txt')#¿Leer el archivo especificado?
Los estudiantes atentos encontrarán: Hay un problema oculto en el ejemplo anterior: la cantidad de procesos aumentará Como La cantidad de URL aumenta, la razón por la que no usamos el grupo de procesos multiprocesamiento.Pool para controlar la cantidad de procesos aquí es que el multiprocesamiento.Pool entra en conflicto con gevent y no se puede usar al mismo tiempo, pero los estudiantes interesados pueden estudiar gevent.po.
ol este grupo de rutinas.