Mic*_*ach 10 python ssh multithreading module paramiko
将以下内容放入一个文件hello.py(easy_install paramiko如果你还没有):
hostname,username,password='fill','these','in'
import paramiko
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()
Run Code Online (Sandbox Code Playgroud)
适当填写第一行.
现在输入
python hello.py
Run Code Online (Sandbox Code Playgroud)
你会看到一些ls输出.
现在改为输入
python
Run Code Online (Sandbox Code Playgroud)
然后从解释器类型中
import hello
Run Code Online (Sandbox Code Playgroud)
瞧!它挂了!如果你将代码包装在一个函数中foo,它将取消它import hello; hello.foo().
为什么Paramiko在模块初始化时使用时会挂起? Paramiko如何知道它首先在模块初始化期间被使用?
Jim*_*imB 18
Paramiko使用单独的线程进行底层传输.你永远不应该有一个产生线程的模块作为导入的副作用.据我所知,有一个可用的导入锁,所以当你的模块中的子线程尝试另一次导入时,它可以无限期地阻塞,因为你的主线程仍然持有锁.(可能还有其他问题,我也不知道)
通常,模块在导入时不应具有任何类型的副作用,否则您将获得不可预测的结果.只是用这个__name__ == '__main__'技巧推迟执行,你会没事的.
[编辑]我似乎无法创建一个简单的测试用例来重现这个死锁.我仍然认为这是导入的线程问题,因为auth代码正在等待永远不会触发的事件.这可能是paramiko或python中的一个错误,但好消息是,如果你正确地做事,你不应该看到它;)
这是一个很好的例子,为什么你总是想要最小化副作用,以及为什么函数式编程技术变得越来越普遍.
正如JimB指出的那样,当 python 尝试在 ssh 连接尝试期间首次使用时隐式导入解码器时,这是一个导入问题str.decode('utf-8')。有关详细信息,请参阅分析部分。
通常,您应该避免让模块在导入时自动生成新线程,这一点再怎么强调也不为过。如果可以,尽量避免使用魔法模块代码,因为它几乎总是会导致不需要的副作用。
如前所述,解决您的问题的简单而明智的方法是将您的代码放在一个if __name__ == '__main__':主体中,该主体仅在您执行此特定模块时才会执行,并且不会在此 mmodule 被其他模块导入时执行。
(不推荐)另一个解决方法是在调用之前在代码中执行一个虚拟的 str.decode('utf-8') SSHClient.connect()- 请参阅下面的分析。
那么这个问题的根本原因是什么呢?
分析(简单密码认证)
提示:如果你想在 python import 和 set 中调试线程 threading._VERBOSE = True
paramiko.SSHClient().connect(.., look_for_keys=False, ..)为您的连接隐式生成一个新线程。如果您打开paramiko.transport.[Thread-5 ] [paramiko.transport ] DEBUG : starting thread (client mode): 0x317f1d0L
这基本上是作为SSHClient.connect(). 当client.py:324::start_client()被调用时,将创建一个锁transport.py:399::event=threading.Event()和线程启动transport.py:400::self.start()。请注意,该start()方法随后将执行该类的transport.py:1565::run()方法。
transport.py:1580::self._log(..)打印我们的日志消息“启动线程”,然后继续transport.py:1584::self._check_banner()。
check_banner做一件事。它检索 ssh 横幅(来自服务器的第一个响应)transport.py:1707::self.packetizer.readline(timeout)(请注意,超时只是套接字读取超时),最后检查换行符,否则超时。
如果收到服务器横幅,它会尝试对响应字符串进行 utf-8 解码packet.py:287::return u(buf),这就是死锁发生的地方。在u(s, encoding='utf-8')做了str.decode(“UTF-1”)和进口隐含encodings.utf8在encodings:99通过encodings.search_function在进口僵局结束了。
因此,一个肮脏的解决方法是只导入一次 utf-8 解码器,以免由于模块导入副作用而阻止该特定导入。( ''.decode('utf-8'))
使固定
脏修复-不推荐
import paramiko
hostname,username,password='fill','these','in'
''.decode('utf-8') # dirty fix
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()
Run Code Online (Sandbox Code Playgroud)
很好的修复
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()
Run Code Online (Sandbox Code Playgroud)