2014-12-23 8 views
1

루비에서 Timeout 모듈을 발견하여 테스트하고 싶었습니다. 내가 http://ruby-doc.org/stdlib-2.1.1/libdoc/timeout/rdoc/Timeout.html 여기Ruby Timeout Module - Timeout이 실행되지 않습니다.

에서 자신의 공식 소스 코드를 보면서 나는 몇 가지 벤치 마크 테스트를했다

require 'timeout' 
require 'benchmark' 
numbers = [*1..80] 
Timeout::timeout(5) { numbers.combination(5).count } 

=> 24040016 

있었고, 다음을 가지고 코드입니다.

10.828000  0.063000  10.891000  11.001676 

문서에 따르면이 메서드는 블록이 5 초 내에 실행되지 않으면 예외를 반환한다고 가정합니다. 시간 프레임 내에서 실행되면 코드 블록의 결과를 반환합니다.

5 초가 아닌 1 초로 타임 아웃을 시도했는데 여전히 결과가 반환됩니다. 코드 블록.

는 여기이 작동하지 않는 이유에 신비화 오전 공식 문서

timeout(sec, klass=nil) 
Performs an operation in a block, raising an error if it takes longer than sec seconds to complete. 

sec: Number of seconds to wait for the block to terminate. Any number may be used, 
including Floats to specify fractional seconds. A value of 0 or nil will execute the 
block without any timeout. 

klass: Exception Class to raise if the block fails to terminate in sec seconds. Omitting 
will use the default, Timeout::Error 

입니다.

답변

2

문제는 MRI (Matz 's Ruby Implementation) 스레드 스케줄링이 작동하는 방식입니다. MRI는 GIL (Global Interpreter Lock)을 사용합니다. 실제로는 한 번에 하나의 스레드 만 실행됩니다.

예외가 있지만 대다수의 경우 한 번에 하나의 스레드 만 루비 코드를 실행합니다.

일반적으로 MRI는 일정한 간격으로 스레드를 시간 분할하여 각 스레드가 실행되도록하기 때문에 100 % CPU를 소비하는 과중한 계산 중에도이를 알지 못합니다.

하지만 시간 슬라이스가 활성화되지 않은 경우와 Ruby 스레드가 Ruby 코드 대신 원시 C 코드를 실행하는 경우가 있습니다.

[7] pry(main)> show-source Timeout#timeout 
From: /opt/ruby21/lib/ruby/2.1.0/timeout.rb @ line 75: 

75: def timeout(sec, klass = nil) #:yield: +sec+ 
76: return yield(sec) if sec == nil or sec.zero? 
77: message = "execution expired" 
78: e = Error 
79: bl = proc do |exception| 
80:  begin 
81:  x = Thread.current 
82:  y = Thread.start { 
83:   begin 
84:   sleep sec 
85:   rescue => e 
86:   x.raise e 
87:   else 
88:   x.raise exception, message 
89:   end 
90:  } 
91:  return yield(sec) 
92:  ensure 
93:  if y 
94:   y.kill 
95:   y.join # make sure y is dead. 
96:  end 
97:  end 
98: end 
99: ... 
1xx: end 
: 우리가 Timeout.timeout가 구현되는 방식이 지식을 결합하면

[1] pry(main)> show-source Array#combination 
From: array.c (C Method): 

static VALUE 
rb_ary_combination(VALUE ary, VALUE num) 
{ 
    ... 
} 

우리가 무슨 일이 일어나고 있는지 단서를 얻을 시작할 수 있습니다

지금 그렇게 Array#combination 순수 C로 구현되어 발생

Array.combination을 실행중인 코드는 실제로 시간 초과 스레드가 sleep sec 라인 84에서 실행되기 전에 실제로 실행되기 시작합니다. 코드는 91에서 yield(sec)까지 실행됩니다.

는 실행 순서가 실제로하게 의미

확인 시간 제한 스레드가이 시간 제한 예외 이번에 트리거 가능성이 가장 높은 것이다이, 시도 할 수 처음 시작될 수 있도록하기 위해
1: [thread 1] numbers.combination(5).count 
    # ...some time passes while the combinations are calculated ... 
2: [thread 2] sleep 5 # <- The timeout thread starts running sleep 
3: [thread 1] y.kill # <- The timeout thread is instantly killed 
         # and never times out. 

:

Timeout::timeout(5) { Thread.pass; numbers.combination(5).count } 

Thread.pass을 실행하면 MFC 스케줄러가 combination C 코드가 실행되기 전에 82 행에서 코드를 시작하고 실행할 수 있기 때문입니다. 그러나이 경우에도 GIL 때문에 combination이 종료 될 때까지 예외가 트리거되지 않습니다.

이 문제를 해결할 방법이 없습니다. 대신에 JRuby와 같은 것을 사용해야 할 것입니다. JRuby는 실제 동시 스레드를 가지고 있습니다. 또는 스레드 대신 Process에서 combination 계산을 실행할 수 있습니다.

관련 문제