Techioz Blog

Ruby Thread オブジェクトでコードのブロックをラップする

概要

コードの一部を Thread.new ブロック ステートメントにグローバルに挿入する必要がある gem 機能を実装する必要があります。その理由についての議論はここでやめましょう:)

次のように定義すると:

module MyThread 

    def new(*args, &block)
        puts "i'm in Thread.new"
        super(*args, &block)
    end

end

Thread.singleton_class.prepend MyThread

そして以下を作成します:

t = Thread.new {
    puts "I'm async thread"
}
t.join

両方のメッセージが表示されます。問題は、メインスレッドから新しいスレッドに何かを渡す必要があるため、そのようなシナリオではブロックを変更する必要があるという問題があります。それがないと、MyThread.new に何かを入れても、メインスレッドでのみ実行されます。通常、Ruby コードを読み取ることでそのようなものをリバース エンジニアリングできますが、Thread は Ruby で定義されておらず、見たとおり、Ruby のラッパーのない純粋な C API です。

したがって、問題は、メインスレッドからグローバルに新しいスレッドに何かを渡し、そこでブロックをラップする新しいスレッドコードを定義するオプションがあるかどうかです。おそらく本当に汚いハックがないわけではありませんが、何かが欠けているかもしれません。

事前にお願いします!

解決策

問題を正しく理解していれば、モジュールによって「挿入」されたコードはメインスレッドで実行されます: (.new ではなく #initialize にパッチを当てるためにコードを少し簡略化しました)

module MyThread
  def initialize(*args, &block)
    puts "Module is #{Thread.current}"
    super(*args, &block)
  end
end
Thread.prepend MyThread

puts "  Main is #{Thread.current}"
Thread.new { puts " Block is #{Thread.current}" }.join

出力:

  Main is #<Thread:0x00007f88e807bc78 run>
Module is #<Thread:0x00007f88e807bc78 run>
 Block is #<Thread:0x00007f88e20cc7c8 test.rb:10 run>

…しかし、ブロックと同じように、他のスレッドで実行したいとします。

そのためには、それを super に渡すブロックに移動する必要があります。ブロック引数は変更できないため、新しいブロックを作成し、渡されたブロックを内部から呼び出す必要があります。

module MyThread
  def initialize(*args, &block)
    super(*args) do |*inner_args|
      puts "Module is #{Thread.current}"
      block.call(*inner_args)
    end
  end
end
Thread.prepend MyThread

puts "  Main is #{Thread.current}"
Thread.new { puts " Block is #{Thread.current}" }.join

出力:

  Main is #<Thread:0x00007f927907bc88 run>
Module is #<Thread:0x00007f926203fb50 test.rb:3 run>
 Block is #<Thread:0x00007f926203fb50 test.rb:3 run>