Python解释器阻塞多线程DNS请求?
我最近玩了一下Python和线程,发现即使在多线程的脚本中,DNS请求还是会阻塞。看看下面这个脚本:
from threading import Thread
import socket
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()
我不太清楚超时时间具体有多长,但运行这个脚本时发生了以下情况:
- 所有线程都开始了,我看到了输出信息
- 每隔一段时间,就有一个线程显示完成,而不是所有线程同时完成
- 线程是一个接一个地完成,而不是同时完成(超时时间对所有线程都是一样的!)
所以我猜这可能和GIL(全局解释器锁)有关?显然,线程并没有同时执行任务,每次只能尝试一个连接。
有没有人知道有什么解决办法?
(asyncore没有帮助,我现在也不想用twisted)
难道就不能用Python完成这个简单的小任务吗?
问候,Tom
编辑:
我在MacOSX上运行这个,我让我的朋友在Linux上试了一下,他实际上得到了我想要的结果。他的socket.connects()调用立即返回,即使在没有线程的环境下。而且即使他把socket设置为阻塞,并将超时时间设置为10秒,所有线程还是同时完成。
有人能解释一下吗?
3 个回答
2
使用 Twisted Names 以异步方式发送DNS请求:
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()
2
如果合适的话,你可以使用 multiprocessing
模块来实现基于进程的并行处理。
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())
15
在某些系统上,getaddrinfo这个函数不是线程安全的。Python认为这些系统包括FreeBSD、OpenBSD、NetBSD、OSX和VMS。在这些系统上,Python会专门为网络数据库(也就是getaddrinfo及其相关函数)保持一个锁。
所以,如果你不能换操作系统,就需要使用其他的(线程安全的)解析库,比如twisted的库。