在线程内部引发同步错误

时间:2017-01-19 07:26:29

标签: ruby multithreading exception

使用以下脚本

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

有一半时间我从出口0得到预期的“得到的错误”而另一半得到test.rb:3:in block in <main>': err (RuntimeError)

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

我考虑过不使用abort_on_exception = true,但问题是如果第一个帖子在sleep(10)之前有raise,那么第二个线程会立即出错,不会被抓住直到10秒钟(由于threads数组的顺序)。

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

任何想法都将不胜感激。谢谢!

更新

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

2 个答案:

答案 0 :(得分:1)

这里有几个难题。

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

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.'

以上打印

  

哦,不,佛罗多!   这是结束,我唯一的朋友,结束。

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

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:在'阻止'中:哦,不,佛罗多! (RuntimeError)

Frodo在我们加入之前提出了他的例外。

  

哦,不,山姆!   这是结束,我唯一的朋友,结束。

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

  

哦,不,佛罗多!ex.rb:2:在'阻止'中:哦,不,山姆! (RuntimeError)

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

  

哦,不,山姆!   这是结束,我唯一的朋友,end.ex.rb:1:在'block in'中:哦,不,佛罗多! (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

答案 1 :(得分:0)

看了@ndn关于将每个线程包装在自己的救援中的想法。看起来这样可以解决问题。这是他的修改示例,它不会阻止在连接时执行。

@threads = []
def execute_safely_concurrently(&work)
  begin
    @threads << Thread.new(&work)
  rescue RuntimeError => e
    puts "Child Thread Rescue: #{e}"
  end
end

execute_safely_concurrently do
  Thread.current.abort_on_exception = true
  sleep(3)
  raise 'Handle me, bitte 1!'
end

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

begin
  @threads.each(&:join)
rescue RuntimeError => e
  puts "Main Thread Rescue: #{e}"
end