From: keiju / Rational.Com (石塚圭樹)

> >|ところで、スレッドセーフになるための要件にはどんなものが
> >|あるでしょうか ?
> >|今度なにか作るときは気をつけなくては。
> >
> >Ruby本には石塚さんによるスレッドの競合を避けるための丁寧な解
> >説が載ってます.
> 
> 丁寧って言えば丁寧かもしれませんが, 要件ってなっているかと言えばそうじゃ
> ないような... 
> 
> # 詳しくは出てのお楽しみと言うことで(^^;;;


ERb では、eRubyスクリプトの印刷結果を文字列と取り出す機能を
持っていて、これをスレッドセーフにしたいと考えています。


現在の実装は $> に、write すると文字列に蓄えていくオブジェクトを入れ
スクリプト終了時に文字列を返すようにしています。


グローバル変数 $> を変更するというと、きっとスレッドセーフに
なれないと思うのですが、どうでしょうか?


$> への代入自体を捉えてスレッド毎のコンテキスト(?)に保存しては‥と考え、
次のようなスクリプトを書いてみて試したのですが、
eRuby スクリプトに Thread があるとうまくできない気がします。

ほかになにか良い方法はあるでしょうか?

# ruby-1.2 にはないけど callcc を使ったらどうにかなるかなぁ。


#!/usr/local/bin/ruby

require 'thread'

class DefOut
  def initialize(out)
    @default = out
    @writers = Hash.new
  end

  def write(s)
    out = @writers.fetch(Thread.current, @default)
    out.write(s)
  end


  def set(obj)
    @writers[Thread.current] = obj
    cleanup
  end

  def cleanup
    @writers.delete_if do |k, v|
      not k.alive?
    end
  end

  @defout = DefOut.new($>)
  $> = @defout
  @th = Thread.new {
    while true
      sleep 60
      $>.cleanup
    end
  }

  trace_var('$>') do 
    @defout.set($>)
    $> = @defout
  end
end

class StringIO
  def initialize(str='')
    @str = str.to_s
  end
  attr :str

  def write(str)
    @str.concat(str)
  end

  def as_stdout(safe=nil)
    t = Thread.new {
      out = StringIO.new
      $> = out
      $SAFE = safe if safe
      yield
      out.str
    }
    t.value
  end
  module_function :as_stdout
end

if __FILE__ == $0
  tab = []
  
  tab.push Thread.new {
    StringIO.as_stdout do
      sleep 2
      print "abc\n"
    end
  }

  tab.push Thread.new {
    StringIO.as_stdout do 
      sleep 1
      print "123\n"
    end
  }

  tab.each do |t|
    $stderr.print t.value
  end
end