这两个函数是 io.BufferedWriter 类的底层实现,会被 print 借居调用。细心不雅察可以发明,它们都有着类似的构造:
- ENTER_BUFFERED(self)
- // ...
- PyErr_CheckSignals();
- // ...
- LEAVE_BUFFERED(self)
ENTER_BUFFERED 是一个宏,会测验测验申请无壅塞线程锁以包管函数不会被重入:
- #define ENTER_BUFFERED(self) \
- ( (PyThread_acquire_lock(self->lock, 0) ? \
- 1 : _enter_buffered_busy(self)) \
- && (self->owner = PyThread_get_thread_ident(), 1) )

至此,本相已经大年夜白了。
Python Signal Handler 的调用会被延后,但机会不止在两个字节码之间,而是可能涌如今任何处所。
本相
当旌旗灯号中断产生在 _bufferedwriter_flush_unlocked 或 _io_BufferedWriter_write_impl 中时,这两个函数中的 PyErr_CheckSignals 会直接唤起 Python Signal Handler,而此时由 ENTER_BUFFERED 上的锁尚未解开,若 Python Signal Handler 中又有 print 函数调用,则会导致再次 ENTER_BUFFERED 上锁掉败,大年夜而抛出异常。时序图如下:

- import sys
- import subprocess
- # ...
- celery_process = subprocess.Popen(
- ['celery', '-A', 'XXX', 'worker'],
- stdout=subprocess.PIPE,
- stderr=sys.stderr
- )
- try:
- # Start and wait for server process
- except KeyboardInterrupt:
- # Ctrl + C pressed
- celery_process.terminate()
- celery_process.wait()
思虑
为什么不将 Python Signal Handler 调用的地点同一在一个处所,而是分布在法度榜样的遍地呢?浏览相干代码,我认为有两点原因:
旌旗灯号中断会使某些体系调用行动异常,大年夜而使体系调用的调用者不知若何处理,此时须要调用 Signal Handler 进行可能的状况恢复。一个例子是 write 体系调用,旌旗灯号中断会导致数据部分写回,与此相干的一大年夜批 I/O 函数(包含出问题的 _bufferedwriter_flush_unlocked 和 _io_BufferedWriter_write_impl)便只能响应地调用 PyErr_CheckSignals。
某些函数须要做计算密集型义务,为了防止 Python Signal Handler 的调用被过长地延后(其实主如果为了及时响应键盘中断,防止法度榜样无法早年台停止),必须合时地检查并调用 Python Signal Handler。一个例子是 Objects/longobject.c 中的诸函数,longobject.c 定义了 Python 特有的无穷长整型,其相干的运算可能耗时相当长,必须做如许的处理。
总结
解决
异常产生的机会有随机性
因为第一条,Python Signal Handler 中尽量都应用 可重入的 的函数,以避免奇怪的问题。可重人道可以大年夜文档获知,也可以结合定义由源码揣摸出来。
有疑问,翻源码。人会撒谎,代码不会。
推荐阅读
CIO半月刊第十九期|李克强总理再提制造业转型,51CTO带你摸底制造业
51CTO诚邀您9月23号和秒拍/国美/美团元专家一路聊智能CDN的优化之路,抓紧时光哦! 【编辑推荐】CIO半月刊第十四期|查访过30多位CIO,我们得出如许的结论CIO半月刊第十五期|某有名旅游企业>>>详细阅读
本文标题:一个Reentrant Error引发的对Python信号机制的探索和思考
地址:http://www.17bianji.com/lsqh/37408.html
1/2 1

网友点评
精彩导读
科技快报
品牌展示