在系统调用中捕获/阻塞SIGINT

8 投票
2 回答
8759 浏览
提问于 2025-04-15 23:48

我写了一个网络爬虫,想通过键盘来停止它。我不希望程序在我中断时直接崩溃,而是需要先把数据保存到硬盘上。我也不想捕捉 KeyboardInterruptedException,因为这样可能会导致数据不一致。

我现在的解决办法是定义一个信号处理器,专门用来捕捉 SIGINT 信号,并设置一个标志;在主循环的每次迭代中,都会检查这个标志,看看是否需要处理下一个网址。

不过,我发现如果系统在执行 socket.recv() 的时候我发送了中断信号,就会出现这样的情况:

^C
Interrupted; stopping...  // indicates my interrupt handler ran
Traceback (most recent call last):
  File "crawler_test.py", line 154, in <module>
    main()
  ...
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/socket.py", line 397, in readline
    data = recv(1)
socket.error: [Errno 4] Interrupted system call

然后进程就完全退出了。为什么会这样呢?有没有办法防止中断影响到系统调用?

2 个回答

3

如果你不想让你的套接字调用被打断,可以在设置信号处理程序后,关闭打断的功能。

signal.signal(<your signal here>, <your signal handler function here>)
signal.siginterrupt(<your signal here>, False)

在信号处理函数中设置一个标志,比如使用 threading.Event(),然后在你的主处理函数中检查这个标志,优雅地结束你的爬虫程序。

这里有一些背景信息:

9

socket.recv() 这个函数其实是在底层调用了一个叫 recv 的C语言函数。这个函数会在等待接收数据的时候,如果进程收到了一个叫 SIGINT 的信号,就会返回一个错误代码 EINTR。这个错误代码在C语言中可以用来判断 recv() 返回的原因,不是因为有新数据,而是因为收到了 SIGINT 的信号。不过在Python中,这个错误代码会被转化成一个异常,而这个异常如果没有被处理,就会导致你的程序崩溃,并显示出错误追踪信息。解决这个问题的方法很简单,就是捕获 socket.error,然后检查错误代码,如果它等于 errno.EINTR,就悄悄地忽略这个异常。大概可以这样写:

import errno

try:
    # do something
    result = conn.recv(bufsize)
except socket.error as (code, msg):
    if code != errno.EINTR:
        raise

撰写回答