2009-07-31 9 views
8

Acabo de jugar un poco con python y threads, y me di cuenta incluso de que en una secuencia de comandos multiproceso, las solicitudes de DNS son bloqueantes. Considere el siguiente script:¿El intérprete de Python bloquea las solicitudes de DNS multiproceso?

de enhebrar la importación de rosca toma importación

class Connection(Thread): 
    def __init__(self, name, url): 
     Thread.__init__(self) 
     self._url = url 
     self._name = name 

    def run(self): 
     print "Connecting...", self._name 
     try: 
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
      s.setblocking(0) 
      s.connect((self._url, 80)) 
     except socket.gaierror: 
      pass #not interested in it 
     print "finished", self._name 


if __name__ == '__main__': 
    conns = [] 
    # all invalid addresses to see how they fail/check times 
    conns.append(Connection("conn1", "www.2eg11erdhrtj.com")) 
    conns.append(Connection("conn2", "www.e2ger2dh2rtj.com")) 
    conns.append(Connection("conn3", "www.eg2de3rh1rtj.com")) 
    conns.append(Connection("conn4", "www.ege2rh4rd1tj.com")) 
    conns.append(Connection("conn5", "www.ege52drhrtj1.com")) 

    for conn in conns: 
     conn.start() 

No sé exactamente cuánto tiempo el tiempo de espera es, pero cuando se ejecuta este ocurre lo siguiente:

  1. sus temas comienzan y obtengo mis impresiones
  2. Cada xx segundos, un hilo muestra terminado, en lugar de todos a la vez
  3. T Los hilos terminan secuencialmente, no todos a la vez (tiempo de espera = ¡lo mismo para todos!)

Entonces, ¿mi única conjetura es que esto tiene que ver con el GIL? Obviamente, los hilos no realizan su tarea al mismo tiempo, solo se intenta una conexión a la vez.

¿Alguien sabe una forma de evitar esto?

(asyncore no ayuda, y yo preferiría no usar trenzado por ahora) ¿No es posible obtener este pequeño y sencillo acto realizado con Python?

Saludos, Tom

edición:

estoy en MacOSX, Acabo de dejar mi amigo ejecutar esto en Linux, y de hecho lo hace obtener los resultados que deseaba obtener. Su socket.connects() regresa de inmediato, incluso en un entorno sin hilos. E incluso cuando establece las tomas en el bloqueo y el tiempo de espera en 10 segundos, todos sus subprocesos terminan al mismo tiempo.

¿Alguien puede explicar esto?

+1

has necesitado simplemente utilizando socket.getaddrinfo (anfitrión, puerto) para ver si eso tiene la misma limitación? Desafortunadamente no puedo replicar esto, porque es un problema de DNS. En la mayoría de los casos, debe obtener un "gaierror:" (-2, "Nombre o servicio no conocido") bastante rápido. – JimB

+0

sí He intentado esto, y tiene la misma limitación. – Tom

+0

Estoy bastante seguro de que OSX usa getaddrinfo de las librerías BSD, que probablemente tenga la restricción mencionada por a continuación. – JimB

Respuesta

15

En algunos sistemas, getaddrinfo no es seguro para subprocesos. Python cree que algunos de estos sistemas son FreeBSD, OpenBSD, NetBSD, OSX y VMS. En esos sistemas, Python mantiene un bloqueo específicamente para netdb (es decir, getaddrinfo y amigos).

Así que si no puede cambiar los sistemas operativos, tendrá que usar una biblioteca de resolución diferente (hilo seguro), como twisted.

+2

+1 uso retorcido! – nosklo

+0

Necesito esto hecho bastante rápido, pero acabo de pedir un libro en retorcido, a la gente parece gustarle, no puede serlo malo, después de todo. Trataré de investigarlo un poco, pero últimamente estoy muy ocupado. – Tom

+0

Existen otras bibliotecas de resolución de Python, como dnspython y pydns, que pueden ser más simples de usar que retorcidas. y no son completamente seguros para subprocesos (creando un nuevo socket UDP para cada solicitud de DNS, y por lo tanto potencialmente agotando los números de puerto), pero eso puede no ser un problema si no realiza muchas consultas. –

2

si es adecuado se puede utilizar el módulo de multiprocessing para permitir basados ​​en procesos paralelismo

import multiprocessing, socket 

NUM_PROCESSES = 5 

def get_url(url): 
    try: 
     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
     s.setblocking(0) 
     s.connect((url, 80)) 
    except socket.gaierror: 
     pass #not interested in it 
    return 'finished ' + url 


def main(url_list): 
    pool = multiprocessing.Pool(NUM_PROCESSES) 
    for output in pool.imap_unordered(get_url, url_list): 
     print output 

if __name__=="__main__": 
    main(""" 
      www.2eg11erdhrtj.com 
      www.e2ger2dh2rtj.com 
      www.eg2de3rh1rtj.com 
      www.ege2rh4rd1tj.com 
      www.ege52drhrtj1.com 
      """.split()) 
+0

es esa una respuesta o una pregunta? : P – Tom

+0

Bueno, contiene código ... ¡así que una respuesta! –

+0

No está disponible en 2.5. Funciona perfectamente – jack

1

peticiones DNS de forma asincrónica utilizando Enviar Twisted Names:

import sys 
from twisted.internet import reactor 
from twisted.internet import defer 
from twisted.names import client 
from twisted.python import log 

def process_names(names): 
    log.startLogging(sys.stderr, setStdout=False) 

    def print_results(results): 
     for name, (success, result) in zip(names, results): 
      if success: 
       print "%s -> %s" % (name, result) 
      else: 
       print >>sys.stderr, "error: %s failed. Reason: %s" % (
        name, result) 

    d = defer.DeferredList(map(client.getHostByName, names), consumeErrors=True) 
    d.addCallback(print_results) 
    d.addErrback(defer.logError) 
    d.addBoth(lambda _: reactor.stop()) 

reactor.callWhenRunning(process_names, """ 
    google.com 
    www.2eg11erdhrtj.com 
    www.e2ger2dh2rtj.com 
    www.eg2de3rh1rtj.com 
    www.ege2rh4rd1tj.com 
    www.ege52drhrtj1.com 
    """.split()) 
reactor.run() 
Cuestiones relacionadas