Python线程中的死锁
我正在用Python实现一个简单的端口扫描器。它的工作原理是创建一些工作线程,这些线程会扫描放在队列里的端口。扫描的结果会保存在另一个队列中。当所有端口都扫描完毕后,线程和应用程序应该结束。但是问题来了:对于少量端口,一切都运行得很好,但如果我尝试扫描200个或更多的端口,应用程序就会陷入死锁。我不知道为什么会这样。
class ConnectScan(threading.Thread):
def __init__(self, to_scan, scanned):
threading.Thread.__init__(self)
self.to_scan = to_scan
self.scanned = scanned
def run(self):
while True:
try:
host, port = self.to_scan.get()
except Queue.Empty:
break
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((host, port))
s.close()
self.scanned.put((host, port, 'open'))
except socket.error:
self.scanned.put((host, port, 'closed'))
self.to_scan.task_done()
class ConnectScanner(object):
def scan(self, host, port_from, port_to):
to_scan = Queue.Queue()
scanned = Queue.Queue()
for port in range(port_from, port_to + 1):
to_scan.put((host, port))
for i in range(20):
ConnectScan(to_scan, scanned).start()
to_scan.join()
有没有人能看出问题出在哪里?另外,我也希望能得到一些关于如何调试Python中线程问题的建议。
2 个回答
我看不出你的代码有什么明显的问题,但目前的情况是,self.to_scan.get()
会一直等待,不会抛出 Queue.Empty 错误。因为你在启动线程之前已经把要扫描的端口放进了队列,所以你可以把它改成 self.to_scan.get(False)
,这样当所有端口都被处理完后,工作线程就能正确退出。
再加上你有非守护线程(这些线程会在主线程结束后继续保持进程运行),这可能是导致程序卡住的原因。你可以在 to_scan.join()
之后打印一些东西,看看程序是停在这里,还是在进程退出时。
正如 Ray 所说,如果在 self.to_scan.get()
和 self.to_scan.task_done()
之间抛出了除了 socket.error 以外的异常,那么 join
调用会卡住。你可以把这段代码改成使用 try/finally 来确保:
def run(self):
while True:
try:
host, port = self.to_scan.get(False)
except Queue.Empty:
break
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((host, port))
s.close()
self.scanned.put((host, port, 'open'))
except socket.error:
self.scanned.put((host, port, 'closed'))
finally:
self.to_scan.task_done()
一般来说,调试多线程程序是比较棘手的。我尽量避免让程序无限期地阻塞——因为如果超时时间太短导致程序崩溃,总比让它永远等待一个永远不会出现的项目要好。所以我建议你给 self.to_scan.get
、socket.connect
和 to_scan.join
的调用设置超时。
使用 logging
来了解事件发生的顺序——打印输出可能会因为不同线程的交错而混乱,但日志记录是线程安全的。
另外,像 这个方法 可以帮助你获取每个线程的当前堆栈跟踪。
我没有使用过支持调试 Python 中多个线程的调试工具,但这里有一些列出的 调试器。
很可能在 to_scan 队列中的所有项目并没有被全部处理完,而且你没有足够多次调用 task_done 方法,这样就导致 ConnectScanner 没法继续运行。
有没有可能在 ConnectScan.run 运行的时候出现了异常,但你没有捕捉到这个异常,导致你的线程提前结束了?