ruby – 在线程内部引发同步错误

使用以下脚本

threads = [
  Thread.new { Thread.current.abort_on_exception = true; raise 'err' },
  Thread.new { Thread.current.abort_on_exception = true; raise 'err' },
]

begin
  threads.each(&:join)
rescue RuntimeError
  puts "Got Error"
end

有一半时间我得到预期的“Got Error”,退出0,另一半得到test.rb:3:在< main>‘中的块:err(RuntimeError).

不应该救援能够处理这个吗?如果没有什么可能是两个线程同时引发错误的替代解决方案?

我已经考虑过不使用abort_on_exception = true但是问题是如果第一个线程有,比如说在加注之前睡眠(10),第二个线程会立即出错,直到10秒钟才会被捕获(由于线程数组的顺序).

Ruby MRI版本:
ruby 2.4.0p0(2016-12-24修订版57164)[x86_64-darwin15]

任何想法将不胜感激.谢谢!

更新

jruby-9.1.6.0似乎没有这个问题.可能是因为它固有的线程安全性.它始终打印Got Error,没有任何异常.不幸的是,JRuby不适合我们.

最佳答案 这里有几个难题.

首先,程序只等待主线程完成:

Thread.new { Thread.current.abort_on_exception = true; raise 'Oh, no!' }

puts 'Ready or not, here I come'

以上可能会或可能不会引起错误.

其次,如果你加入一个线程,那个线程引发的异常将由#join方法的连接线程重新引发:

gollum = Thread.new { raise 'My precious!!!' }

begin
  gollum.join
rescue => e
  # Prints 'My precious!!!'
  puts e.message
end

此时,执行将返回到已加入的线程.它不再加入导致错误的线程或任何其他线程.它没有加入其他线程的原因是因为你当时只能加入一个线程. threads.each(& join)实际上将你连接到第一个,当它结束时 – 到第二个,依此类推:

frodo = Thread.new { raise 'Oh, no, Frodo!' }
sam   = Thread.new { raise 'Oh, no, Sam!' }

begin
  [frodo, sam].each(&:join)
rescue => e
  puts e.message
end

puts 'This is the end, my only friend, the end.'

以上打印

Oh, no, Frodo!
This is the end, my only friend, the end.

现在让我们把它放在一起:

frodo = Thread.new { Thread.current.abort_on_exception = true; raise 'Oh, no, Frodo!' }
sam   = Thread.new { Thread.current.abort_on_exception = true; raise 'Oh, no, Sam!' }

begin
  [frodo, sam].each(&:join)
rescue => e
  puts e.message
end

puts 'This is the end, my only friend, the end.'

这里可能发生很多事情.重要的是,如果我们设法加入(在此之前我们没有得到错误),救援将从主线程中捕获异常,从任何线程设法先提升它然后在救援后继续.之后,主线程(以及程序)可能会或可能不会在另一个线程引发其异常之前完成.

让我们检查一些可能的输出

ex.rb:1:in `block in ‘: Oh, no, Frodo! (RuntimeError)

在我们加入之前,佛罗多提出了他的例外.

Oh, no, Sam!
This is the end, my only friend, the end.

我们加入后,Sam是第一个提出错误的人.在主线程中打印出错误消息后,我们也打印了结束.然后主线程完成,在Frodo可以提出他的错误之前.

Oh, no, Frodo!ex.rb:2:in `block in ‘: Oh, no, Sam! (RuntimeError)

我们成功加入.佛罗多是第一个筹集,我们获救和印刷. Sam在我们打印结束之前加注了.

Oh, no, Sam!
This is the end, my only friend, the end.ex.rb:1:in `block in ‘: Oh, no, Frodo! (RuntimeError)

(很少)我们设法得救了. Sam首先提出错误,然后从主线程中打印出来.我们打印了结束.在打印之后,但在主线程终止之前,Frodo也成功地抓住了他的错误.

至于一个可能的解决方案,你需要尽可能多的救援,因为有可能引起的线程.请注意,我还将线程创建放在受保护的块中,以确保我们在连接之前捕获潜在的错误:

def execute_safely_concurrently(number_of_threads, &work)
  return if number_of_threads.zero?

  begin
    Thread.new(&work).join
  rescue => e
    puts e
  end

  execute_safely_concurrently(number_of_threads.pred, &work)
end

execute_safely_concurrently(2) do
  Thread.current.abort_on_exception = true
  raise 'Handle me, bitte!'
end
点赞