2009-07-31 7 views
8

Je viens de jouer un peu avec python et les threads, et réalisé même dans un script multithread, les requêtes DNS bloquent. Considérez le script suivant:L'interpréteur Python bloque les requêtes DNS multithread?

de filetage import fil Prise d'importation

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() 

Je ne sais pas exactement combien de temps le délai d'attente est, mais l'exécution de ce qui suit se produit:

  1. toutes les discussions commencent et je reçois mes impressions
  2. Toutes les xx secondes, un fil affiche fini, au lieu de tout à la fois
  3. T Les threads finissent séquentiellement, pas tous en même temps (timeout = le même pour tous!)

Donc, mon seul doute est que cela a à voir avec le GIL? Évidemment, les threads n'effectuent pas leur tâche simultanément, une seule connexion est tentée à la fois.

Est-ce que quelqu'un sait un moyen de contourner cela?

(asyncore ne aide pas, et je préfère ne pas utiliser tordu pour l'instant) est-il pas possible d'obtenir cette petite chose simple fait avec python?

Salutations, Tom

modifier:

Je suis sur MacOSX, je viens de laisser mon ami exécuter sur linux, et il ne fait obtenir les résultats que je voulais obtenir. Son retour de socket.connects() immédiatement, même dans un environnement non Threaded. Et même quand il met les douilles à bloquer, et le délai d'attente à 10 secondes, tous ses fils finissent en même temps.

Quelqu'un peut-il expliquer cela?

+1

Avez-vous essayé simplement en utilisant socket.getaddrinfo (hôte, port) pour voir si cela a la même limitation? Je ne peux malheureusement pas reproduire cela, car c'est un problème de DNS. Dans la plupart des cas, vous devriez obtenir un "gaierror: (-2, 'Name ou service not known')" plutôt rapidement. – JimB

+0

oui j'ai essayé ceci, et il a la même limitation. – Tom

+0

Je suis à peu près sûr qu'OSX utilise getaddrinfo des bibliothèques BSD, qui a probablement la restriction listée ci-dessous par . – JimB

Répondre

15

Sur certains systèmes, getaddrinfo n'est pas thread-safe. Python pense que certains de ces systèmes sont FreeBSD, OpenBSD, NetBSD, OSX et VMS. Sur ces systèmes, Python maintient un verrou spécifiquement pour le netdb (c'est-à-dire getaddrinfo et ses amis). Par conséquent, si vous ne pouvez pas changer de système d'exploitation, vous devrez utiliser une bibliothèque de résolveurs différente (thread-safe), telle que twisted.

+2

+ 1 utilisation tordu! – nosklo

+0

J'ai besoin de cela fait assez rapidement, mais je viens de commander un livre sur torsadé, les gens semblent vraiment aimer, ne peux pas être si Je vais essayer d'y aller un peu, mais je suis vraiment occupé dernièrement – Tom

+0

Il existe d'autres bibliothèques de résolveurs Python, comme dnspython et pydns, qui peuvent être plus simples à utiliser que tordu. y ne sont pas totalement sécurisés pour les threads (création d'un nouveau socket UDP pour chaque requête DNS, ce qui risque d'épuiser les numéros de port), mais cela ne pose pas de problème si vous n'effectuez pas beaucoup de requêtes. –

2

si elle est appropriée, vous pouvez utiliser le module multiprocessing pour permettre

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

est-ce une réponse ou une question? : P – Tom

+0

Eh bien, il contient du code ... donc une réponse! –

+0

Il n'est pas disponible en 2.5. Cela fonctionne parfaitement. – jack

1

Envoyer les requêtes DNS parallélisme à base de processus utilisant de manière asynchrone 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()