考虑这个小的python脚本 odd-read-blocking.py
:
#!/usr/bin/python
import signal
import sys
sig = None
def handler(signum, frame):
global sig
sig = signum
signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGTERM, handler)
x = sys.stdin.read(3)
print 'signal', sig
print 'read bytes', len(x)
exit(0)
我运行此操作,并用两个标准输入数据('a' ' n'(馈送它:
> echo a | ./odd-read-blocking.py
signal None
read bytes 2
>
罚款。
现在,我用相同的两个字节(通过将'a' ' n'键入其标准输入(进行馈送。请注意,标准输入尚未达到EOF,并且有可能有更多数据。因此,读取块,正如它期望的一个字节一样。我在脚本上使用 ctrl c 。
> ./odd-read-blocking.py
a
^Csignal 2
read bytes 2
>
很好。我们看到已经读取了两个字节并收到了信号2。
现在我打开一个标准输入流,但不要在其上发送任何字节。读取块如预期。如果我现在使用 ctrl c 在脚本上,它将继续坐在那里等待。读取不会中断。Sigint将不会被处理。
> ./odd-read-blocking.py
^C
这里什么都没有。脚本仍在运行(似乎在读取时被阻止(。
现在击中一次返回,然后 ctrl c 再次:
^Csignal 2
read bytes 1
>
因此,只有在收到至少某些数据(在这种情况下为单个 n'(的标准输入后,脚本会按照我的期望,并正确中断被阻止的读取并告诉我它已收到信号2和读1个字节
替代性1:而不是使用 ctrl c ,如上所示,我尝试使用同样的东西使用来自单独的终端的kill pid
。行为是相同的。
替代2:而不是如上所述使用壳标准输入,而是这样做了:
> sleep 2000 | ./odd-read-blocking.py
使用kill pid
将Sigterm发送到odd-read-blocking.py
进程时,我会得到相同的行为。在这里,脚本过程只能使用Sigkill杀死(9(。
为什么读取的读取不中断,当它阻塞尚未为空但仍处于活动状态的标准输入流?
?我觉得这个奇怪。谁没有?谁可以解释?
简短版本
如果python信号处理程序引发了放弃正在进行的file.read
的例外,则所有已经读取的数据is oft >。(任何异步异常(如默认KeyboardInterrupt
(,除非您有办法掩盖它,否则基本上不可能防止这种失败。(
为了最大程度地减少对此的需求, file.read
返回(即,,,用一个比请求的较短字符串(在被信号中断时,请注意,这是对EOF和非EOF和非eof和non-的添加的添加阻止已记录的I/O案例!但是,当它尚无数据时,它不能执行此操作,因为它返回了空字符串以指示eof。
详细信息
一如既往,理解这样的行为的方法是用strace
。
阅读(2(
当该过程被阻止时,当信号到达时,实际的read
系统调用存在困境。首先,(c(信号处理程序被调用—但是,由于任何两个说明之间都可能发生这种情况,所以除了设置标志(或写入自pipe(之外,它几乎无法做到。那呢?如果设置了SA_RESTART
,则恢复呼叫;否则…
如果尚未传输数据,read
可能会失败,客户可以检查其信号标志。特殊EINTR
失败了,以澄清I/O。
如果已经将某些数据写入(用户空间(缓冲区,则不能仅仅返回"失败",因为数据将丢失— client不知道在缓冲区中有多少(如果有(数据有多少(如果有(。因此,它只是返回成功(到目前为止读取的字节数(!这样的简短读数总是可能性的:客户必须再次致电read
才能检查其已达到文件末端。(就像 file.read
一样,对0字节的简短读取将 be eof。(因此,客户必须在每次读取后都必须检查其信号标志,无论它是否成功。(请注意,这仍然不是完全可靠的,但是对于许多交互式用例来说已经足够好了。(
file.read((
系统调用不是全部:毕竟,终端的正常配置在看到新线后立即返回。Python  2的低级file.read
是fread
的包装器,如果一个人很短,它将发布另一个read
。但是,当读取使用EINTR
失败时,fread
会提早返回,file.read
致电您的(Python(信号处理程序。(如果向其添加输出,即使file.read
不返回,您也会看到它立即为您发送的每个信号调用。(
然后面临着类似于系统呼叫的困境:正如所讨论的,简短的读数不能为空,因为这意味着EOF。但是,与C信号处理程序不同,Python可以做任意的工作(包括提出一个例外,以立即中止I/O,以危险的数据丢失为代价(,这被认为是一种方便的简化,接口隐藏可能性EINTR
。因此,fread
呼叫只是默默地重复。
Python 3.5
重试的规则在3.5中更改。现在,io.IOBase.read
即使手头有数据,也可以恢复。这是更一致的,但是它迫使使用异常停止阅读,这意味着您不能选择等待某些数据以免丢失任何已经拥有的数据。非常重量的解决方案是切换到多路复用I/O并使用signal.set_wakeup_fd()
;这具有允许Sigint影响主线程的额外优点,而不必打扰其他所有内容。