¿Por qué es necesario el multiproceso de Python?
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 el cambio lleva más tiempo. 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 Utilice completamente el rendimiento de la máquina, pero si hay demasiados procesos, se producirán errores frecuentes y no vale la pena ganar.
Sin embargo, en circunstancias especiales (especialmente tareas con uso intensivo de E/S), los subprocesos múltiples son más fáciles de usar que los procesos múltiples.
Por ejemplo: dados 2 millones de URL, debe capturar y guardar la página correspondiente a cada URL. En este momento, usar múltiples procesos por sí solo definitivamente tendrá malos resultados. ¿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 que puede aumentar la velocidad 10 veces en comparación con 1 , que son 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 un bloque y cambia al subproceso 3, etc. Después de todo, 10. Los subprocesos están bloqueados, esto El proceso está bloqueado y el proceso no puede continuar ejecutándose hasta que se bloquee un determinado subproceso, por lo que la mejora de la velocidad puede ser aproximadamente 10 veces (la sobrecarga causada por el cambio de subprocesos se ignora aquí y la mejora real no debería alcanzar 10 veces)), pero lo que debe tenerse en cuenta es que el cambio de subprocesos también tiene una sobrecarga, por lo que los subprocesos múltiples no se pueden iniciar infinitamente (definitivamente no es confiable abrir 2 millones de subprocesos) 3. Multiproceso + multiproceso: esto Es increíble, en términos generales se dice que muchas personas usan este método en multiproceso, cada proceso puede ocupar una CPU y el subproceso múltiple omite la espera bloqueada hasta cierto punto, por lo que es mejor usarlo que el subproceso múltiple. un solo proceso, por ejemplo, abrimos 10 procesos, cada uno con subprocesos de 200 W. La velocidad de ejecución es teóricamente más de 10 veces más rápida que un solo proceso con subprocesos de 200 W (por qué es más de 10 veces en lugar de 10 veces, la razón principal es). que la CPU cambia a subprocesos de 200 W) El consumo de subprocesos es definitivamente mucho mayor que 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é:
p >
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: vea aquí que el uso de corrutinas no está limitado por la sobrecarga del subproceso. Una vez intenté ejecutar 200.000 URL en una corrutina de proceso único y no hubo ningún problema.
Por lo tanto, 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, lo que evita la sobrecarga). de conmutación de CPU y puede utilizar completamente varias 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:
#-*- coding=utf-8 -*-
solicitudes de importación
desde proceso de importación multiprocesamiento
importar gevent
desde gevent importar mono; utf8')
def fetch(url):
prueba:
s = request.Session()
r = s. get(url,timeout=1)#Obtenga la página aquí
excepto excepción,e:
imprimir e
return ''
def process_start(tasks):
gevent.joinall(tasks)#Usa corrutina para ejecutar
def task_start(filepath, flag = 100000):#Iniciar cada 10W URL Un proceso con open(filepath,'r') as lector: #Leer la URL del archivo dado url = lector.readline().strip()
task_list = []#Esta lista se utiliza para almacenar tareas de rutina
i = 0 #Contador, registre cuántas URL se agregan a la cola de rutina mientras url!='':
i += 1
task_list.append (gevent.spawn(fetch,url,queue))#Cada vez que se lee la URL, la tarea se agrega a la cola de rutina si i == flag:#Un cierto número de URL inicia un proceso y ejecuta p = Proceso(destino =process_start,args=(task_list,))p.start()
task_list = [] #Restablecer cola de rutinas
i = 0 #Restablecer contador
url = lector.readline().strip()
if task_list not []: #Si quedan URL en la cola de tareas después de salir del bucle p = Process(target=process_start, args=(task_list ,))#Coloque todas las URL restantes en el último proceso para ejecutar p.start()
if __name__ == '__main__':
task_start( './testData.txt ')# Los estudiantes cuidadosos que lean el archivo especificado encontrarán que hay un problema oculto en el ejemplo anterior: la cantidad de procesos continuará aumentando a medida que aumenta la cantidad de URL. Aquí no utilizamos el multiprocesamiento del grupo de procesos. La razón por la que .Pool controla la cantidad de procesos es que el multiprocesamiento. Pool entra en conflicto con gevent y no se puede usar al mismo tiempo. Sin embargo, los estudiantes interesados pueden estudiar el grupo de rutinas gevent.pool.
Hay otro problema: las URL procesadas por cada proceso son acumulativas en lugar de independientes. Por ejemplo, el primer proceso procesará 100.000 URL, el segundo proceso procesará 20.000 URL, y así sucesivamente. Finalmente, se determinó que el problema fue causado por gevent.joinall(). Los estudiantes interesados pueden estudiar por qué es así. Sin embargo, la solución a este problema es: el proceso principal solo es responsable de leer la URL y escribirla en la lista. Al crear el proceso hijo, la lista se pasa directamente al proceso hijo, y el proceso hijo crea la rutina misma. .
De esta forma no habrá problema de acumulación