From: "H.Yamamoto" Date: 2005-12-29T14:43:40+09:00 Subject: [ruby-dev:28159] Re: thread based generator.rb 山本です。 >こちらはインタプリタの話になるので私にはわかりませんが、 >ファイナライザでどうにかできないかといじってみた結果、それらしく動くように >なりました。(まだ適当なコードで、コミットできる状態ではありません) とりあえず改善してみました。ただ、 > g = Generator.new { |g2| > for i in 'A'..'C' > g2.yield i > end > > g2.yield 'Z' > } は、ブロック引数に Generator ではなく、他のオブジェクトが渡るように なっています。ブロック内では元々 Generator#yield しか呼べない(はず) なので、この方がいいと思うのですが、前述のスコープの問題があります。 本当は g = Generator.new { for i in 'A'..'C' yield i end yield 'Z' } としたほうがわかりやすいと思うのですが、うまくいきませんでした。 あと、メモリは sleep 0 を入れるのでも使用量が変化しなくなりました。(???) Index: generator.rb =================================================================== RCS file: /src/ruby/lib/generator.rb,v retrieving revision 1.5 diff -u -w -b -p -r1.5 generator.rb --- generator.rb 29 Dec 2005 02:29:26 -0000 1.5 +++ generator.rb 29 Dec 2005 05:34:18 -0000 @@ -56,16 +56,23 @@ class Generator include Enumerable - # Creates a new generator either from an Enumerable object or from a - # block. - # - # In the former, block is ignored even if given. - # - # In the latter, the given block is called with the generator - # itself, and expected to call the +yield+ method for each element. + class GeneratorProxy + def initialize(impl) + @impl = impl + end + + # Yields an element to the generator. + def yield(value) + @impl.yield(value) + nil # ??? + end + end + + class GeneratorImpl # internal use only + def initialize(enum = nil, &block) if enum - @block = proc{|g| enum.each{|value| g.yield value}} + @block = proc{|proxy| enum.each{|value| proxy.yield value}} else @block = block end @@ -75,7 +82,7 @@ class Generator Thread.stop Thread.critical = true begin - @block.call(self) # exception safe? + @block.call(GeneratorProxy.new(self)) rescue @main_thread.raise $! ensure @@ -83,22 +90,19 @@ class Generator Thread.critical = false end end - self end - # Yields an element to the generator. + # should be called on @loop_thread + def yield(value) - if Thread.current != @loop_thread - raise RuntimeError.new("Generator#yield must be called in Generator.new{|g| ... }") - end @queue << value @main_thread.wakeup Thread.stop Thread.critical = true - self end - # Returns true if the generator has reached the end. + # should be called on @main_thread + def end? if @queue.empty? Thread.critical = true @@ -116,37 +120,77 @@ class Generator @queue.empty? end + def index + @index + end + + def next + raise EOFError.new("no more elements available") if end? + @index += 1 + @queue.shift + end + + def current + raise EOFError.new("no more elements available") if end? + @queue.first + end + + def rewind + initialize(nil, &@block) if @index.nonzero? + end + + def create_finalizer + proc { @loop_thread.kill } + end + + end + + # Creates a new generator either from an Enumerable object or from a + # block. + # + # In the former, block is ignored even if given. + # + # In the latter, the given block is called with the generator + # itself, and expected to call the +yield+ method for each element. + def initialize(enum = nil, &block) + @impl = GeneratorImpl.new(enum, &block) + ObjectSpace.define_finalizer(self, @impl.create_finalizer) + self + end + + # Returns true if the generator has reached the end. + def end? + @impl.end? + end + # Returns true if the generator has not reached the end yet. def next? - !end? + !@impl.end? end # Returns the current index (position) counting from zero. def index - @index + @impl.index end # Returns the current index (position) counting from zero. def pos - @index + @impl.index end # Returns the element at the current position and moves forward. def next - raise EOFError.new("no more elements available") if end? - @index += 1 - @queue.shift + @impl.next end # Returns the element at the current position. def current - raise EOFError.new("no more elements available") if end? - @queue.first + @impl.current end # Rewinds the generator. def rewind - initialize(nil, &@block) if @index.nonzero? + @impl.rewind self end @@ -251,12 +295,12 @@ class TC_Generator < Test::Unit::TestCas end def test_block2 - g = Generator.new { |g| + g = Generator.new { |y| for i in 'A'..'C' - g.yield i + y.yield i end - g.yield 'Z' + y.yield 'Z' } assert_equal(0, g.pos)