商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Ruby信号处理详解

Ruby信号处理详解

  发布于2026-06-29 阅读(0)

扫一扫,手机访问

Ruby使用Process.kill发送信号

Process.kill(signal, pid, ...) → integer

先来看Process.kill的作用:发送指定的信号给一个或多个进程或进程组。它的行为取决于pid参数的取值——这几个规则其实很直白:

Ruby信号处理详解

  • 如果目标pid>0,那就发送信号给指定PID的那个进程。
  • 如果目标pid=0,信号会发给调用kill的进程所在进程组里的所有进程。
  • 如果目标pid<0,情况稍微复杂一点,得看操作系统。以Linux为例:
  • pid=-1时,信号会发给除了init进程(PID=1)以外的所有进程——当然,没有权限的进程会被跳过。
  • pid<-1时,信号发给-pid这个进程所在的进程组里的所有进程。比如-3000,就是把信号发给PID为3000的进程所在的整个进程组。

那么Process.kill的第一个参数——信号——怎么指定?

  • 信号可以是字符串格式的名字,也可以是数值。比如INTSIGINT1都是有效的。
  • 如果信号带个负号(比如-2-INT),意思是发送给进程所在的进程组,而不是具体的进程。不过Linux不支持这种写法。
  • 如果信号是0,那就不是真的发送信号,而是探测能不能把信号发过去——可以用来检查目标进程是否存在,或者当前进程是否有权限管理它。
pid = fork do
  sleep 300
end
# ...
Process.kill("HUP", pid)
Process.wait

Ruby使用trap()设置信号处理程序

在Ruby里,捕获信号并定义处理逻辑,用的是Kernel.trapSignal.trap,这俩完全等价。可以同时为多个信号设置不同的trap。

Signal.trap(0, proc { puts "Terminating: #{$$}" })
Signal.trap("CLD")  { puts "Child died" }
fork && Process.wait
=begin
Terminating: 27461
Child died
Terminating: 27460
=end

trap的第一个参数是要监控的信号,同样可以是字符串名(比如SIGINT)、省略SIG前缀的名字(比如INT),或者数值(比如2)。Ruby还支持一个特殊的信号0,对应的字符串名字是EXITSIGEXIT——它会在进程退出时触发。

第二个参数或者代码块,就是捕获信号后要执行的内容。第二个参数有几种特殊的字符串值:

  • IGNORESIG_IGN:忽略这个信号。
  • DEFAULTSIG_DFL:按Ruby的默认处理规则来。
  • EXIT:以状态码0退出当前进程。
  • SYSTEM_DEFAULT:按系统的默认信号处理规则,一般是以状态码141退出。

避免信号覆盖

用第三方库的时候,经常不确定它有没有给某个信号注册处理程序——就算知道它注册了,自己也想追加逻辑,但不想覆盖人家的处理。怎么办?巧用trap的返回值就行。每次调用trap,它都会返回之前已经注册的处理程序(一个Proc对象)。不过要注意:有些信号的初始处理程序是字符串DEFAULT,而不是Proc对象,所以得做类型判断:

# 第一次定义INT的信号处理程序
first_trap = trap('INT') { 
  first_trap.call if first_trap.is_a? Proc
  puts "first_trap" 
}

# 第二次定义INT的信号处理程序
old_trap = trap('INT') { 
  old_trap.call if old_trap.is_a? Proc   # 调用第一次定义的信号处理程序
  puts "old trap"  # 本次trap时执行的逻辑
}
# 定义好之后,old_trap为第一次定义的信号处理程序

# 之后按下CTRL+C触发INT信号的信号处理程序

多线程信号注册问题

如果是在多线程环境里注册信号处理程序,有一个重要规则:信号处理程序总是注册在所在进程的main线程中——哪怕你是在其他线程里调用的trap()。来看个例子:

pid = fork do
  puts "main Thread: #{Thread.current}"
  Thread.new {
    puts "new Thread: #{Thread.current}"
    trap("TERM", proc { puts "Signal: #{Thread.current}" })
    sleep 2
  }
  sleep 2
end

sleep 1
Process.kill 'SIGTERM', pid
=begin
main Thread: #
new Thread: #
Signal: #
=end

输出很说明问题:信号处理实际是在main线程里执行的,而不是在那个新线程里。

子进程继承信号处理程序

子进程会从父进程那里继承信号处理程序。这一点可能出乎意料,但确实如此:

trap 'TERM', proc { puts "Signal: #{Process.pid}" }
puts "Parent: #{Process.pid}"

pid = fork do
  sleep 30
end

puts "Child: #{pid}"
Process.kill 'TERM', pid
=begin
Parent: 2872
Child: 2901
Signal: 2901
=end

可以看到,子进程的TERM信号处理程序直接继承自父进程——这在设计多进程架构时是个需要留意的细节。

本文转载于:https://www.jb51.net/article/244904.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

产品推荐

热门关注