实现 Signal.trap
回调的注意事项¶ ↑
与在 C 或大多数其他语言中实现信号处理程序一样,传递给 Signal.trap
的所有代码都必须是可重入的。如果您不熟悉可重入性,您需要在阅读本文档的其余部分之前在 维基百科或其他地方阅读相关内容。
最重要的是,“线程安全”并不能保证可重入性;而且诸如 Mutex#lock 和 Mutex#synchronize 之类的常用于线程安全的方法甚至会阻止可重入性。
Ruby VM 的一个实现细节¶ ↑
Ruby VM 会延迟运行 Signal.trap
回调,直到其内部数据结构安全为止,但它不知道您的代码中的数据结构何时安全。 Ruby 通过注册短 C 函数(仅使用 async-signal-safe 函数)作为信号处理程序来实现延迟信号处理。这些短 C 函数仅执行足够的告诉 VM 在主 Ruby Thread
中稍后运行通过 Signal.trap
注册的回调。
在 Signal.trap
代码块中调用的不安全方法¶ ↑
如有疑问,请将下面未列为安全的任何内容都视为不安全。
-
Mutex#lock、Mutex#synchronize 以及任何使用它们的代码都明确不安全。这包括标准库中的
Monitor
,它使用 Mutex 来提供可重入性。 -
带代码块的
Dir.chdir
-
当
IO#sync
为 false 时,任何IO
写入操作;包括IO#write
、IO#write_nonblock
、IO#puts
。管道和套接字默认为“IO#sync = true”,因此可以安全地写入它们,除非IO#sync
已被禁用。 -
File#flock
,因为底层的 flock(2) 调用未由 POSIX 指定
Signal.trap
代码块内部常用的安全操作¶ ↑
-
局部变量、实例变量和类变量的赋值和检索
-
不执行代码块的常见
Array
、Hash
、String
、Struct
操作通常是安全的;但要注意如果其他地方正在发生迭代。 -
Thread::Queue#push
和Thread::SizedQueue#push
(自 Ruby 2.1 起) -
通过
Thread.new
/Thread.start 创建一个新的Thread
可用于规避信号处理程序内部 Mutex 的不可用性 -
Signal.trap
在传递给Signal.trap
的代码块内使用是安全的 -
Integer
和Float
上的算术运算 (‘+’, ‘-’, ‘%’, ‘*’, ‘/’)此外,信号处理程序不会在两次连续的局部变量访问之间运行,因此在信号处理程序中对
Integer
和Float
类使用 “+=” 和 “-=” 等快捷方式不会触发数据竞争。
在 Signal.trap
内部安全的系统调用包装方法¶ ↑
由于 Ruby 具有许多 async-signal-safe C 函数 的包装器,因此许多 IO
、File
、Dir
和 Socket
方法的相应包装器是安全的。
(不完整列表)
-
Dir.chdir
(没有代码块参数)
...