gpt4 book ai didi

python - 在具有自定义信号处理程序的情况下在 python 2 中读取 sys.stdin 时出现奇怪的阻塞行为

转载 作者:太空宇宙 更新时间:2023-11-04 04:45:51 25 4
gpt4 key购买 nike

考虑这个小的 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,并且可能会有更多数据到来。所以读取 block ,因为它期望多一个字节。我在脚本上使用 Ctrl+C

> ./odd-read-blocking.py 
a
^Csignal 2
read bytes 2
>

很好。我们看到已经读取了两个字节并收到了信号 2。

现在我打开一个标准输入流,但不向其发送任何字节。读取 block 如预期。如果我现在在脚本上使用 Ctrl+C,它会一直坐在那里等待。读取不会中断。 SIGINT 将不会被处理。

> ./odd-read-blocking.py 
^C

这里什么都没有。脚本仍在运行(在读取时似乎被阻止)。

现在按回车一次,然后再次按 Ctrl+C:

^Csignal 2
read bytes 1
>

因此,只有在其标准输入上至少接收到一些数据(在本例中为单个“\n”)后,脚本才会按我预期的方式运行并正确中断被阻止的读取并告诉我它已收到信号 2 和读取 1 个字节。

备选方案 1:如上所示,我没有使用 Ctrl+C,而是使用 kill <em>pid</em> 尝试了同样的操作从一个单独的终端。行为相同。

备选方案 2:我没有使用上述的 shell 标准输入,而是这样做了:

> sleep 2000 | ./odd-read-blocking.py

使用 kill <em>pid</em> 时将 SIGTERM 发送到 odd-read-blocking.py过程我得到相同的行为。这里,脚本进程只能使用SIGKILL(9)来杀死。

为什么读取在一个尚未空但仍处于事件状态的标准输入流上阻塞时没有被中断?

我觉得这很奇怪。谁没有?谁能解释一下?

最佳答案

简短版

如果 Python 信号处理程序抛出异常以放弃正在进行的 file.read,则任何已读取的数据都会丢失。 (任何异步异常,比如默认的 KeyboardInterrupt,基本上不可能阻止这种故障,除非你有一个 way to mask it。)

为了尽量减少对此的需求,file.read 在被信号中断时提前返回(,字符串比请求的更短)——注意这除了记录的 EOF 和非阻塞 I/O 案例之外!但是,当它还没有数据时它不能这样做,因为它返回空字符串以指示 EOF。

详情

一如既往,理解这种行为的方法是使用 strace

阅读(2)

当信号到达而进程被阻塞时,实际的 read 系统调用会进退两难。首先,调用 (C) 信号处理程序——但由于这可能发生在任何两条指令之间,因此除了设置标志(或写入自管道)之外,它几乎无能为力。然后呢?如果设置了SA_RESTART,则恢复通话;否则……

如果尚未传输任何数据,read 可能会失败,客户端可以检查其信号标志。它无法通过特殊的 EINTR 来澄清 I/O 实际上没有任何问题。

如果一些数据已经写入(用户空间)缓冲区,它不能只返回“失败”,因为数据会丢失——客户端无法知道缓冲区中有多少(如果有的话)数据.所以它只返回成功(到目前为止读取的字节数)!像这样的短读取总是有可能的:客户端必须再次调用 read 以检查它是否已到达文件末尾。 (就像 file.read 一样,0 字节的短读取将 EOF。)因此,客户端必须在每次读取后检查其信号标志,无论是否成功. (请注意,这仍然不是 perfectly reliable ,但对于许多交互式用例来说已经足够了。)

文件.读取()

系统调用并不是全部:毕竟,终端的正常配置让它在看到换行符后立即返回。 Python 2 的低级 file.read 是一个 wrapper for fread , 如果一个很短,它将发出另一个 read 。但是当读取失败并显示 EINTR 时,fread 会提前返回并且 file.read 会调用您的 (Python) 信号处理程序。 (如果你向它添加输出,你会看到它会立即为你发送的每个信号调用,即使 file.read 没有返回。)

然后它面临着与系统调用类似的困境:正如所讨论的,短读不能为空,因为它意味着 EOF。然而,与 C 信号处理程序不同的是,Python 信号处理程序可以执行任意工作(包括引发异常以立即中止 I/O,如开头所述,代价是冒着数据丢失的风险),并且它被认为是对接口(interface)隐藏可能性 EINTR。所以 fread 调用只是默默地重复。

python 3.5

重试规则changed in 3.5 .现在,即使手头有数据,io.IOBase.read 也会恢复;这更一致,但它 forces使用异常来停止读取,这意味着您不能选择等待某些数据以免丢失您已经拥有的任何数据。非常重量级的解决方案是切换到多路复用 I/O 并使用 signal.set_wakeup_fd();这具有允许 SIGINT 影响主线程的额外优势,而不必费心在所有其他线程中屏蔽它。

关于python - 在具有自定义信号处理程序的情况下在 python 2 中读取 sys.stdin 时出现奇怪的阻塞行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49653837/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com