2011-02-11 14 views
9

Aquí hay un código que ilustra mi problema:Python: ¿cómo puedo hacer que este código sea asíncrono?

def blocking1(): 
    while True: 
     yield 'first blocking function example' 

def blocking2(): 
    while True: 
     yield 'second blocking function example' 

for i in blocking1(): 
    print 'this will be shown' 

for i in blocking2(): 
    print 'this will not be shown' 

Tengo dos funciones que contienen while True bucles. Esto arrojará datos que luego registraré en algún lugar (muy probablemente, en una base de datos sqlite).

He estado jugando con el roscado y lo he puesto en funcionamiento. Sin embargo, realmente no me gusta ... Lo que me gustaría hacer es hacer que mis funciones de bloqueo sean asincrónicas. Algo como:

def blocking1(callback): 
    while True: 
     callback('first blocking function example') 

def blocking2(callback): 
    while True: 
     callback('second blocking function example') 

def log(data): 
    print data 

blocking1(log) 
blocking2(log) 

¿Cómo puedo lograr esto en Python? He visto que la biblioteca estándar viene con asyncore y el gran nombre en este juego es Twisted, pero ambos parecen usarse para socket IO.

¿Cómo puedo sincronizar mis funciones de bloqueo no relacionadas con el socket?

+0

'Lo Me gustaría hacer es hacer que mis funciones de bloqueo sean asincrónicas. Eso no tiene ningún sentido. O deseas que tu función se bloquee o quieres que sea asincrónica. Si lo quiere asíncrono, use un hilo. No veo cuál es el problema. – Falmarri

+0

Quiero que mis funciones de bloqueo sean no bloqueantes – dave

+0

Hay muchas preguntas que deben ser respondidas acerca de cómo se intercala la ejecución de esas funciones, no solo entre ellas, sino con todo el código subsiguiente que se ejecuta. Esto es para lo que es el sistema operativo, y por qué generalmente quieres usar un hilo para algo así. ¿Puede explicar lo que no le gusta de enhebrar? Este es exactamente el tipo de problema que se creó para resolver. –

Respuesta

1

Si no desea utilizar el enrutamiento completo del sistema operativo, puede intentar Stackless, que es una variante de Python que agrega muchas características interesantes, incluyendo "microthreads". Hay varios buenos examples que le serán útiles.

+0

Parece interesante, pero prefiero seguir usando Python – dave

+1

Python sin aditamentos es python. – Falmarri

+0

Interpreté el texto de introducción como un intérprete de Python alternativo. Echaré otro vistazo ahora. Aclamaciones. – dave

9

Puede usar generadores para la multitarea cooperativa, pero tiene que escribir su propio bucle principal que pase el control entre ellos.

Aquí hay un ejemplo (muy simple) usando el ejemplo anterior:

def blocking1(): 
    while True: 
     yield 'first blocking function example' 

def blocking2(): 
    while True: 
     yield 'second blocking function example' 


tasks = [blocking1(), blocking2()] 

# Repeat until all tasks have stopped 
while tasks: 
    # Iterate through all current tasks. Use 
    # tasks[:] to copy the list because we 
    # might mutate it. 
    for t in tasks[:]: 
     try: 
      print t.next() 
     except StopIteration: 
      # If the generator stops, remove it from the task list 
      tasks.remove(t) 

Se podría mejorar aún más al permitir que los generadores para producir nuevos generadores, que a su vez podrían añadirse a las tareas, pero esperemos que este ejemplo simplificado dará la idea general.

+0

Esta parece ser una buena solución. Sin embargo, si los dos generadores usaran libpcap para escuchar en dos interfaces separadas y devuelvan los paquetes olfateados, ¿perdería paquetes debido a que solo un generador regresa en un momento dado? – dave

+0

@mike: Sí. Si va a hacer algo relacionado con E/S, como el rastreo de paquetes, entonces el uso de huellas reales es una mejor solución (a menos que la biblioteca de E/S en cuestión proporcione una API nativa que no sea de bloqueo). – shang

+1

La verdadera respuesta aquí depende de los detalles. Si está bloqueando llamadas de E/S y ese es el problema, entonces necesita transformar * esas llamadas * para que ya no bloqueen. No puede hacer nada con el resto de su código para hacerlo asíncrono (sin hilos) a menos que tenga una versión controlada por eventos de esas funciones. En otras palabras, vea la respuesta de Keith y "use twisted". – Glyph

2

El armazón trenzado no es solo un zócalo. Tiene adaptadores asíncronos para muchos escenarios, incluida la interacción con subprocesos. Recomiendo echar un vistazo más de cerca a eso. Hace lo que intentas hacer.

28

Una función de bloqueo es una función que no regresa, pero que aún deja el proceso inactivo: no se puede completar más trabajo.

Nos está pidiendo que hagamos sus funciones de bloqueo sin bloqueo. Sin embargo, a menos que esté escribiendo un sistema operativo, no tiene ninguna función de bloqueo. Es posible que tenga funciones que se bloqueen porque hacen llamadas a las llamadas al sistema de bloqueo, o puede tener funciones que "bloquean" porque hacen una gran cantidad de cálculos.

Haciendo que el antiguo tipo de función no bloquee es imposible sin que la llamada del sistema subyacente sea no bloqueante. Dependiendo de lo que llame ese sistema, puede ser difícil hacerlo sin bloqueo sin agregar también un bucle de evento a su programa; no solo necesita realizar la llamada y que no se bloquee, también debe realizar otra llamada para determinar que el resultado de esa llamada se entregará en algún lugar donde pueda asociarlo.

La respuesta a esta pregunta es un programa python muy largo y una gran cantidad de explicaciones de las diferentes interfaces de sistema operativo y cómo funcionan, pero afortunadamente ya escribí esa respuesta en un sitio diferente; Lo llamé Twisted. Si su tarea particular ya es supported by a Twisted reactor, entonces está de suerte. De lo contrario, siempre que su tarea se corresponda con algún concepto de sistema operativo existente, puede ampliar un reactor para que lo soporte.En términos prácticos, solo hay dos de estos mecanismos: descriptores de archivos en cada sistema operativo sensible y puertos de finalización de E/S en Windows.

En el otro caso, si sus funciones consumen mucha CPU y, por lo tanto, no regresan, en realidad no están bloqueando; su proceso sigue avanzando y trabajando. Hay tres maneras de lidiar con eso:

  • hilos separados
  • procesos separados
  • si tiene un bucle de eventos, escribir una tarea que produce periódicamente, escribiendo la tarea de tal manera que lo hace un poco de trabajo, luego le pide al ciclo de eventos que lo reanude en el futuro cercano para permitir que se ejecuten otras tareas.

En Twisted esta última técnica se puede lograr de varias maneras, pero aquí es un truco sintácticamente conveniente que hace que sea fácil:

from twisted.internet import reactor 
from twisted.internet.task import deferLater 
from twisted.internet.defer import inlineCallbacks, returnValue 

@inlineCallbacks 
def slowButSteady(): 
    result = SomeResult() 
    for something in somethingElse: 
     result.workHardForAMoment(something) 
     yield deferLater(reactor, 0, lambda : None) 
    returnValue(result) 
+2

Gran publicación. He pasado las últimas horas buscando información sobre otro problema de bloqueo de Python Socket y aunque esta publicación pertenece a un contexto diferente (el mío es gevent/greenlets), es una buena introducción para principiantes sobre por qué una rutina puede bloquear en el entorno de Pitón. Gracias por publicar. – Matty

0

El código no está bloqueando. blocking1() y sus iteradores de retorno hermano inmediatamente (no bloquean), y tampoco lo hace un solo bloque de iteración (en su caso).

Si usted quiere “comer” de los dos iteradores uno por uno, no hace su programa de tratar de comer “blocking1()” en su totalidad, antes de continuar ...

for b1, b2 in zip(blocking1(), blocking2()): 
    print 'this will be shown', b1, 'and this, too', b2 
Cuestiones relacionadas