Python线程中的死锁

时间:2011-11-26 01:15:06

标签: python multithreading

我正在尝试用Python实现一个简单的portscanner。它的工作原理是创建许多工作线程,这些线程扫描队列中提供的端口。他们将结果保存在另一个队列中。扫描所有端口后,线程和应用程序应终止。问题在于:对于少量端口,一切正常,但如果我尝试扫描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()

有人看到可能出错的地方吗?另外,我要感谢一些tipp如何在Python中调试这样的线程问题。

2 个答案:

答案 0 :(得分: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.getsocket.connectto_scan.join来电指定超时时间。

使用logging计算正在发生的订单事件 - 打印可以从不同的线程交错,但记录器是线程安全的。

此外,像this recipe这样的东西可以方便地为每个线程转储当前的堆栈跟踪。

我没有使用任何支持在Python中调试多个线程的调试器,但有些列出了here

答案 1 :(得分:1)

很可能不会消耗to_scan队列中的所有项目,也不会调用task_done方法足以解锁ConnectScanner。

可能是在ConnectScan.run的运行时期间抛出异常而你没有捕获并且你的线程过早终止了吗?