栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

py.test成功运行后,模块“线程化”中的KeyError

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

py.test成功运行后,模块“线程化”中的KeyError

我观察到类似的问题,并决定确切地了解发生了什么-让我描述一下我的发现。我希望有人会觉得有用。

短篇故事

它确实与猴子修补

threading
模块有关。实际上,通过在猴子修补线程之前导入线程模块,我可以轻松触发异常。以下两行就足够了:

import threadingimport gevent.monkey; gevent.monkey.patch_thread()

执行时,它会吐出有关忽略的消息

KeyError

(env)czajnik@autosan:~$ python test.py Exception KeyError: KeyError(139924387112272,) in <module 'threading' from '/usr/lib/python2.7/threading.pyc'> ignored

如果交换导入行,问题就消失了。

很长的故事

我可以在这里停止调试,但是我认为值得了解问题的确切原因。

第一步是找到打印有关被忽略异常的消息的代码。我很难找到它(

Exception.*ignored
一无所获),但是在CPython源代码
voidPyErr_WriteUnraisable(PyObject*obj)
中反复探寻,我终于找到了一个在Python
/
error.c中

调用的函数,上面有一个非常有趣的注释:

我决定在的少许帮助下检查谁在调用它,

gdb
只是为了获得以下C级堆栈跟踪:

#0  0x0000000000542c40 in PyErr_WriteUnraisable ()#1  0x00000000004af2d3 in Py_Finalize ()#2  0x00000000004aa72e in Py_Main ()#3  0x00007ffff68e576d in __libc_start_main (main=0x41b980 <main>, argc=2,    ubp_av=0x7fffffffe5f8, init=<optimized out>, fini=<optimized out>,     rtld_fini=<optimized out>, stack_end=0x7fffffffe5e8) at libc-start.c:226#4  0x000000000041b9b1 in _start ()

现在我们可以清楚地看到在执行Py_Finalize时引发了异常-
此调用负责关闭Python解释器,释放分配的内存等。它在退出之前被调用。

下一步是查看

Py_Finalize()
代码(在Python /
pythonrun.c中)。它发出的第一个调用是
wait_for_thread_shutdown()
-值得研究,因为我们知道问题与线程有关。该函数依次调用模块中的
_shutdown
callable
threading
。好的,我们现在可以回到python代码。

看一下,

threading.py
我发现了以下有趣的部分:

class _MainThread(Thread):    def _exitfunc(self):        self._Thread__stop()        t = _pickSomeNonDaemonThread()        if t: if __debug__:     self._note("%s: waiting for other threads", self)        while t: t.join() t = _pickSomeNonDaemonThread()        if __debug__: self._note("%s: exiting", self)        self._Thread__delete()# Create the main thread object,# and make it available for the interpreter# (Py_Main) as threading._shutdown._shutdown = _MainThread()._exitfunc

显然,

threading._shutdown()
调用的责任是加入所有非守护进程线程并删除主线程(无论这是什么意思)。我决定打点补丁
threading.py
-
_exitfunc()
try
/包裹整个身体,
except
并使用traceback模块打印堆栈跟踪。这给出了以下跟踪:

Traceback (most recent call last):  File "/usr/lib/python2.7/threading.py", line 785, in _exitfunc    self._Thread__delete()  File "/usr/lib/python2.7/threading.py", line 639, in __delete    del _active[_get_ident()]KeyError: 26805584

现在我们知道引发异常的确切位置-内部

Thread.__delete()
方法。

阅读

threading.py
一段时间后,故事的其余部分显而易见。该
_active
字典映射线程ID(通过返回的
_get_ident()
),以
Thread
实例,对创建的所有线程。当
threading
模块被加载,实例
_MainThread
类始终是创建并添加到
_active
(即使是明确创建其他线程)。

问题在于,

gevent
猴子的修补方法之一是将
_get_ident()
原始方法映射到原始方法
thread.get_ident()
,然后用猴子修补方法将其替换
green_thread.get_ident()
。显然,两个调用都为主线程返回不同的ID。

现在,如果

threading
在猴子修补之前加载了模块,则在创建实例并将其添加到实例
_get_ident()
时,call返回一个值,而此时又调用另一个值-
因此是in 。
_MainThread``_active``_exitfunc()``KeyError``del_active[_get_ident()]

相反,如果在

threading
加载之前完成了猴子补丁,那么一切都很好-
在将
_MainThread
实例添加到时
_active
_get_ident()
已经进行了补丁,并且在清理时返回了相同的线程ID。而已!

为了确保以正确的顺序导入模块,在猴子补丁调用之前,我向代码中添加了以下代码段:

import sysif 'threading' in sys.modules:        raise Exception('threading module loaded before patching!')import gevent.monkey; gevent.monkey.patch_thread()

我希望您发现我的调试故事很有用:)



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/624662.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号