这是一篇在 Ruby-China 上讨论的比较激烈的帖子,原帖地址 https://ruby-china.org/topics/23766。
原帖属于楼主的经验分享帖,一个关于 sidekiq 的 worker 被全部占用,无法释放的问题。 出现这个问题的原因是,楼主 sidekiq worker 中有一段网络请求的代码,但由于网络请求服务不稳定,导致无响应或者响应很慢,所以,worker 全部都被占用了。
解决这个问题的办法很简单,就是设置 Timeout 时间。
IO 请求都需要设置超时时间。
楼主在原帖中还提到了 sidekiq 常见 Frozen worker 的两种情况:
- DNS lookup - resolving a hostname might hang. This has a serious side effect in MRI of locking up everything because of the way MRI uses DNS by default. The solution is to run require ‘resolv-replace’ in your initializer, which installs a pure Ruby DNS resolver that works concurrently.
- Net::HTTP - unresponsive remote servers can cause a Net::HTTP call to hang and lock up your workers. Set open_timeout to ensure your code raises an exception rather than hanging forever.
楼主的问题属于第2点,但是第1点却更引起我的兴趣,因为它提到了 MRI 的 GIL 。
假设有线程a,b,其中a 调用了一个会被 block的 C 扩展,那么 b 线程也会被 block 吗 ?
第一步: a 线程未调用 C 扩展,执行如下代码:
# test.rb
a = Thread.new do
while true do
puts "aaa"
end
end
b = Thread.new do
while true do
puts "bbb"
end
end
sleep
输出结果:bbb aaa
, 可见, a,b 线程都执行了。
第二步:编写一个会被block的 C 扩展(死循环):
# 一个会被 block的 C 扩展程序
#include "ruby.h"
VALUE BlockExt = Qnil;
VALUE perform(VALUE self);
void Init_block_ext() {
BlockExt = rb_define_class("BlockExt", 0);
rb_define_singleton_method(BlockExt, "run", perform, 0);
}
VALUE perform(VALUE self) {
while(Qtrue){ }
return Qnil;
}
第三步: a 线程调用 C 扩展:
# test.rb
require_relative 'ext/block_ext'
a = Thread.new { BlockExt.run }
b = Thread.new do
while true do
puts "bbb"
end
end
sleep
运行 ruby test.rb
, 你会发现 b 线程也会 block。
出现这个问题的原因是因为 a 线程调用了外部扩展程序,由于 Ruby GIL 的缘故, 同一时间只能有一个线程(a) active, 而由于外部程序 block住了,导致无返回, 从而a程序一直处于active状态,所以b线程一直处于 unactive状态,也就被block住了。
结论:Ruby 多线程都需要注意一些 C Extensions 的 GIL block 住整个进程的问题,自己编写的 C 扩展,一定要确保返回和结束。
代码地址: https://github.com/songjiayang/rc-ext-gil