Proceso
El proceso es la unidad más pequeña de asignación de recursos, con espacio de memoria independiente, que incluye información de registro, montón, pila, segmento de datos, segmento de código, memoria virtual, identificador de archivo, estado de E / S, información de señal, etc., la sobrecarga de conmutación de diferentes procesos es relativamente grande Si bien el proceso es relativamente independiente y estable, generalmente no se ve afectado por otros procesos
La comunicación entre procesos incluye tuberías, colas de mensajes, semáforos, memoria compartida, sockets, etc.
Hilo
El subproceso es la unidad más pequeña de programación del sistema, solo necesita guardar su propia pila, registrar información y otro contenido pequeño, un proceso debe tener al menos un subproceso, la sobrecarga de conmutación de diferentes subprocesos es mucho menor que el cambio del proceso, pero el subproceso no es independiente y lo suficientemente estable como para ser vulnerable Y el impacto de otros hilos
Debido a que diferentes subprocesos comparten la misma memoria, la comunicación entre subprocesos usa directamente la memoria compartida, que es utilizar variables definidas globalmente. Además, los diferentes subprocesos generalmente también necesitan lograr la sincronización y la exclusión mutua a través de bloqueos
Corutina
El sistema operativo programa tanto los procesos como los subprocesos. Aunque la sobrecarga de conmutación de subprocesos es menor que el proceso, si se cambia con frecuencia, seguirá afectando seriamente el rendimiento
El sistema operativo generalmente cambia en tres situaciones
- El programa dura más.
- Preemption de programas con mayor prioridad
- El programa esta bloqueado
En muchas aplicaciones de red, se aceptará una gran cantidad de solicitudes al mismo tiempo. El cálculo de estas solicitudes es muy pequeño. El tiempo principal se gasta en IO, y el más importante es el tiempo de IO de la red, que causa el bloqueo frecuente de IO y el cambio de subprocesos, lo que afecta seriamente Rendimiento
La rutina es resolver el problema de rendimiento del programa con IO como la sobrecarga principal en escenarios de alta concurrencia
Puede ejecutar múltiples corutinas en un hilo. Cuando una corutina llama a un comando que requiere bloqueo de E / S, utilizará E / S asíncrona para evitar que el sistema operativo cambie y luego continúe ejecutando otra rutina. Implementado dentro de un hilo, la sobrecarga de conmutación es muy pequeña, el rendimiento mejorará considerablemente
Tenga en cuenta que la rutina es más obvia en el caso de una gran cantidad de concurrencia de E / S, porque solo en este caso podemos garantizar que la E / S asíncrona esté lista para ejecutarse en cualquier momento. Si la cantidad de E / S es pequeña, como una solicitud en 10 minutos, entonces Después de realizar una operación asincrónica, aún debe esperar a que el IO asincrónico esté listo, lo que seguirá causando el cambio de subprocesos
Tenga en cuenta que la rutina solo cambiará en un caso: IO call
Esta función debe ser implementada por el marco del programa, que es transparente para el sistema operativo y transparente para el programa de aplicación. Carga de trabajo de desarrollo
En el lenguaje Go, esta función es nativa. El lenguaje Go mismo implementa esta función y la admite
a nivel de sintaxis. En el lenguaje Python, esta función es compatible con el paquete gevent.
Lo siguiente habla principalmente sobre las corutinas de Python
rendimiento
El rendimiento es para uso del generador, como el siguiente código
def f(max):
n = 1
while n <= max:
yield n*n
n = n + 1
for i in f(5):
print(i)
Si no usa el rendimiento, entonces la función f necesita devolver una lista, si max es muy grande, entonces necesita crear una memoria grande para poner esta lista, y después de usar el rendimiento, la función se considera como un iterador, f (5) Devuelve un iterador. Activa el iterador cada vez que la instrucción for toma un valor. Cuando el iterador ejecuta el comando de rendimiento, devuelve n * ny detiene la ejecución hasta la próxima vez que se toma el valor for. El iterador comienza desde n = n + 1 Continúe la ejecución para que no importa cuán grande sea el máximo, el uso de la memoria es constante
Dar otro ejemplo
def f():
n = 1
print("f function with yield inside")
while True:
msg = yield n
print("msg: ", msg)
n = n + 1
iter = f()
print("before invoke next")
print("receive: ", next(iter))
print("after invoke next")
print("receive: ", next(iter))
Lo que se devuelve es
before invoke next
f function with yield inside
('receive: ', 1)
after invoke next
('msg: ', None)
('receive: ', 2)
Se puede ver que cuando se llama a iter = f (), no se imprime ninguna información, es decir, la función f () no se ejecuta realmente, pero se devuelve un iterador, cuando se ejecuta la siguiente función (iter) (la siguiente es una función incorporada de Python) , La función f () se ejecuta, y aquí solo se ejecuta hasta obtener n, y luego deja de ejecutarse y devuelve n como resultado (aquí incluso no se ejecuta la asignación de msg, que se discutirá más adelante), cuando la próxima función siguiente, Continuará ejecutándose desde la asignación de msg hasta que alcance el rendimiento nuevamente. Si el iterador se ha ejecutado, la siguiente función informará una excepción StopIteration
Continuar con el siguiente ejemplo
def f():
n = 1
print("f function with yield inside")
while True:
msg = yield n
print("msg: ", msg)
n = n + 1
iter = f()
print("before invoke next")
print("receive: ", next(iter))
print("after invoke next")
print("receive: ", iter.send("from outside"))
Aquí, el segundo siguiente se reemplaza con la función de envío que llama al iterador
devuelve
before invoke next
f function with yield inside
('receive: ', 1)
after invoke next
('msg: ', 'from outside')
('receive: ', 2)
La única diferencia con el ejemplo anterior es que el mensaje impreso no es Ninguno, sino el parámetro de la función de envío. Al igual que el siguiente, la función de envío activa el iterador para continuar la ejecución, pero al mismo tiempo el parámetro se asigna al mensaje como resultado de la declaración de rendimiento.
Los siguientes usos rinden para simular corutinas
def f_0():
n = 5
while n >= 0:
print('[f_0] ' + str(n))
yield
n = n - 1
def f_1():
m = 3
while m >= 0:
print('[f_1] ' + str(m))
yield
m = m - 1
iter_list = [f_0(), f_1()]
while True:
for it in iter_list:
try:
next(it)
except:
iter_list.remove(it)
if len(iter_list) == 0:
break
El resultado es
[f_0] 5
[f_1] 3
[f_0] 4
[f_1] 2
[f_0] 3
[f_1] 1
[f_0] 2
[f_1] 0
[f_0] 1
[f_0] 0
Se puede ver que la función de cambiar continuamente entre las dos funciones se realiza, pero el código es engorroso de escribir
vireillo
greenlet es una biblioteca de extensión C que implementa corutinas nativas en la parte inferior
from greenlet import greenlet
def f_0():
n = 5
while n >= 0:
print('[f_0] ' + str(n))
parent_greenlet.switch()
n = n - 1
def f_1():
m = 3
while m >= 0:
print('[f_1] ' + str(m))
parent_greenlet.switch()
m = m - 1
def parent():
while True:
for task in greenlet_list:
task.switch()
if task.dead:
greenlet_list.remove(task)
if len(greenlet_list) == 0:
break
parent_greenlet = greenlet(parent)
greenlet_list = [greenlet(f_0, parent_greenlet), greenlet(f_1, parent_greenlet)]
parent_greenlet.switch()
Volver
[f_0] 5
[f_1] 3
[f_0] 4
[f_1] 2
[f_0] 3
[f_1] 1
[f_0] 2
[f_1] 0
[f_0] 1
[f_0] 0
El interruptor también se puede pasar por valor, que se pasará al parámetro de función de acuerdo con el estado de ejecución del programa, o al retorno del interruptor
def test1(x, y):
z = gr2.switch(x+y)
print(z)
def test2(u):
print(u)
gr1.switch(42)
print "end"
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch("hello", " world")
Volver
hello world
42
Se puede ver que el final no se imprime porque el principal no está especificado. De manera predeterminada, un extremo vuelve a main y el otro no se ejecutará. Si se especifica el primario, se devolverá el principal después del final
traficado
Greenlet también es más complicado de escribir, y Greenlet solo implementa corutinas, pero no implementa la función de capturar operaciones de E / S y conmutación. De hecho, los cálculos generales no requieren el cambio de corutinas, y el rendimiento no tiene efecto, solo en operaciones de E / S muy concurrentes. Cuando el programa se puede cambiar, su rendimiento mejorará considerablemente
gevent se basa en greenlet y utiliza muchas medidas de optimización, incluido el mecanismo de supervisión de eventos epoll de linux para mejorar el rendimiento de las altas E / S concurrentes. Por ejemplo, cuando un programa greenlet necesita realizar operaciones de E / S de red, se registra como supervisión y conmutación asincrónicas Vaya a otros programas de greenlet, espere a que se complete IO, y luego vuelva a continuar para continuar la ejecución cuando sea apropiado, de modo que cuando el IO sea muy alto, pueda mantener el programa ejecutándose, en lugar de perder tiempo esperando IO, mientras evita el enhebrado Cambio por encima
import gevent
def f_0(param):
n = param
while n >= 0:
print('[f_0] ' + str(n))
gevent.sleep(0.1)
n = n - 1
def f_1(param):
m = param
while m >= 0:
print('[f_1] ' + str(m))
gevent.sleep(0.1)
m = m - 1
g1 = gevent.spawn(f_0, 5)
g2 = gevent.spawn(f_1, 3)
gevent.joinall([g1, g2])
Volver
[f_0] 5
[f_1] 3
[f_0] 4
[f_1] 2
[f_0] 3
[f_1] 1
[f_0] 2
[f_1] 0
[f_0] 1
[f_0] 0
Se puede ver que el código es conciso y claro. En comparación con el programa normal, es reemplazar time.sleep () con gevent.sleep (). Es gevent puede hacer un cambio de rutina donde necesita bloquearse.
En realidad puede ser más simple
import time
import gevent
from gevent import monkey
monkey.patch_all()
def f_0(param):
n = param
while n >= 0:
print('[f_0] ' + str(n))
time.sleep(0.1)
n = n - 1
def f_1(param):
m = param
while m >= 0:
print('[f_1] ' + str(m))
time.sleep(0.1)
m = m - 1
g1 = gevent.spawn(f_0, 5)
g2 = gevent.spawn(f_1, 3)
gevent.joinall([g1, g2])
La aplicación de parches con monkey.patch_all () puede interceptar una gran cantidad de operaciones de E / S, como tiempo de espera, solicitud http, etc., ejecutarlas de forma asincrónica y cambiar las rutinas. Este enfoque permite que la función original se use directamente sin modificación. Para los desarrolladores, las rutinas son transparentes, no es necesario modificar el código específicamente, solo déjelo en paz para que lo cuide
asyncio
Python 3.6 presentó oficialmente la biblioteca asyncio como la biblioteca estándar de Python
Las más importantes son las palabras clave asíncronas y aguardan
async se usa para declarar una función como asincrónica y se puede suspender
await se usa para declarar que el programa está suspendido. Await solo puede seguirlo un programa asincrónico o un objeto con el atributo __await__
import asyncio
import aiohttp
async def f_0(param):
n = param
while n >= 0:
print('[f_0] ' + str(n))
await asyncio.sleep(0.1)
n = n - 1
async def f_1(param):
m = param
while m >= 0:
print('[f_1] ' + str(m))
await asyncio.sleep(0.1)
m = m - 1
loop = asyncio.get_event_loop()
tasks = [
f_0(5),
f_1(3)
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
Volver
[f_0] 5
[f_1] 3
[f_0] 4
[f_1] 2
[f_0] 3
[f_1] 1
[f_0] 2
[f_1] 0
[f_0] 1
[f_0] 0
Otro ejemplo
import asyncio
import aiohttp
async def request(session, url):
async with session.get(url) as response:
return await response.read()
async def fetch(url):
await asyncio.sleep(1)
async with aiohttp.ClientSession() as session:
html = await request(session, url)
print(html)
url_list = [
"http://www.qq.com",
"http://www.jianshu.com",
"http://www.cnblogs.com"
]
tasks = [fetch(url) for url in url_list]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
Puede ver que necesita agregar asíncrono para admitir llamadas asíncronas, y use wait para especificar el lugar donde está suspendido.
Si el código especificado por await no puede suspenderse, se producirá un error
y deberá usarse un método o clase asíncrono específico.
En comparación, gevent puede ser transparente para el programa.
Un programa síncrono normal se puede lograr de forma asincrónica a través de gevent sin ninguna modificación.
Sin embargo, gevent usa el paquete tripartito, y asyncio es la biblioteca estándar de Python, que proporciona soporte a nivel de sintaxis.