Python 如果在加载模块时使用Paramiko,为什么它会挂起?
将以下内容放入一个文件hello.py(如果您没有安装paramiko,请使用Python 如果在加载模块时使用Paramiko,为什么它会挂起?,python,multithreading,ssh,module,paramiko,Python,Multithreading,Ssh,Module,Paramiko,将以下内容放入一个文件hello.py(如果您没有安装paramiko,请使用轻松安装paramiko): 适当地填写第一行 现在输入 python hello.py 您将看到一些ls输出 现在改为打字 python 然后从解释器类型中 import hello 瞧!它挂起来了!如果您将代码包装在函数foo中并执行import hello,它将取消处理;hello.foo() 为什么Paramiko在模块初始化中使用时会挂起Paramiko最初是如何意识到它在模块初始化期间被使用的?Par
轻松安装paramiko
):
适当地填写第一行
现在输入
python hello.py
您将看到一些ls输出
现在改为打字
python
然后从解释器类型中
import hello
瞧!它挂起来了!如果您将代码包装在函数foo
中并执行import hello,它将取消处理;hello.foo()
为什么Paramiko在模块初始化中使用时会挂起Paramiko最初是如何意识到它在模块初始化期间被使用的?Paramiko对底层传输使用单独的线程。 您永远不应该让一个模块产生一个线程作为导入的副作用。据我所知,只有一个导入锁可用,因此当模块中的子线程尝试另一个导入时,它可能会无限期地阻塞,因为主线程仍然持有该锁。(可能还有其他我不知道的问题) 一般来说,模块在导入时不应该有任何副作用,否则您将得到不可预知的结果。只需使用
\uuuu name\uuuu=='\uuuuuu main\uuuu'
技巧推迟执行,就可以了
[编辑]
我似乎无法创建一个复制这种死锁的简单测试用例。我仍然认为这是导入的线程问题,因为身份验证代码正在等待一个永远不会触发的事件。这可能是paramiko或python中的一个bug,但好消息是,如果操作正确,就永远不会看到它;)
这是一个很好的例子,为什么您总是希望最小化副作用,为什么函数式编程技术变得越来越流行。正如所指出的,当python尝试在ssh连接尝试期间第一次使用时隐式导入str.decode('utf-8')
解码器时,这是一个导入问题。有关详细信息,请参见分析部分
一般来说,您应该避免让模块在导入时自动生成新线程。如果可以的话,尽量避免使用魔法模块代码,因为它几乎总是会导致不必要的副作用
中,如果uuuu name_uuuuu==''uuuuuuuu main_uuuu':
主体中,该主体仅在您执行此特定模块时才会执行,而在其他模块导入此mmodule时不会执行SSHClient.connect()
之前,只需在代码中执行一个伪str.decode('utf-8')-请参阅下面的分析threading.\u VERBOSE=True
paramiko.SSHClient()。如果为paramiko.transport
打开调试输出,也可以看到这一点李>
[Thread-5][paramiko.transport]调试:启动线程(客户端模式):0x317f1d0L
这基本上是作为SSHClient.connect()
的一部分完成的。调用client.py:324::start\u client()
时,将创建一个锁transport.py:399::event=threading.event()
,并启动线程transport.py:400::self.start()
。请注意,start()
方法随后将执行类的transport.py:1565::run()
方法
transport.py:1580::self.\u log(..)
打印我们的日志消息“starting thread”,然后进入transport.py:1584::self.\u check\u banner()
check_banner
做了一件事。它检索ssh横幅(来自服务器的第一个响应)transport.py:1707::self.packetizer.readline(超时)
(请注意,超时只是套接字读取超时),并在末尾检查换行符
否则就会超时
如果收到服务器横幅,它将尝试utf-8解码响应字符串packet.py:287::return u(buf)
,这就是死锁发生的地方。u(s,encoding='utf-8')
执行str.decode('utf-i')并隐式导入编码。utf8
在编码中:99
通过编码。搜索功能
最终导致导入死锁
因此,一个糟糕的修复方法是只导入utf-8解码器一次,以避免由于模块导入的副作用而阻塞该特定导入。('.decode('utf-8')
)
修复
import paramiko
if __name__ == '__main__':
hostname,username,password='fill','these','in'
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
脏补丁-不推荐
修复得好
import paramiko
if __name__ == '__main__':
hostname,username,password='fill','these','in'
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
ref”。解码(“utf-8”)对我不起作用,我最终做了这个
from paramiko import py3compat
# dirty hack to fix threading import lock (issue 104) by preloading module
py3compat.u("dirty hack")
我为paramiko实现了一个包装器。
如果在命令行上执行相同的ssh调用,会发生什么?工作正常。当你在一个模块中运行这段代码时,它对你来说正常吗?当你通过命令行运行它时,你是一行一行地运行还是一次全部运行?@jdangel:我想你的意思是当我通过解释器运行它时;在命令行上,它只是“ssh”user@hostls/'。在解释器中,我自己输入击键,所以是一行一行。如果你一次复制整个内容并将其粘贴到解释器中,而不是一行一行地粘贴到解释器中,会发生什么情况。我相信这确实是paramiko中的一个bug(或至少是缺陷)。谢谢你,Jim!(很抱歉,几个月后我才注意到你的答案——StackOverflow还没有一个很好的通知系统!)你能举出一个示例来说明name=='main'的用法吗?@Tectrendz:看看几乎所有的python脚本:+1实际上导入锁比生成它们时更为复杂。如果您遵循最佳实践并在中执行等待(例如signal.pause()
)<
from paramiko import py3compat
# dirty hack to fix threading import lock (issue 104) by preloading module
py3compat.u("dirty hack")