From: Shin-ichiro HARA Date: 2002-04-24T17:51:30+09:00 Subject: [ruby-list:34973] Re: Thread by callcc 原です。 Tanaka Akira writes: >> なるほど、Thread でもできるとは思っていたのですが、面白いですね。 >> Coroutine は、2つのスレッドでのピンポン方式ですね。 > >2つというわけではないです。sync-each でいえば引数の数だけスレッドを作 >ります。 引数の数だけのスレッドが、メインスレッドとピンポンしているわけで すね。メインスレッドを m、他のスレッドを 1, 2, 3 とすると、 [ruby-list:34953] の田中さんの sync-each.rb は([ruby-list:34956] の sync-each2.rb も)、ほぼ m1m2m3m1m2m3... とスレッドがスイッチ するのに対して、最初の [ruby-list:34943] の sync-each.rb は、 m123m123... と round robin 式に動くわけで、対比的に前者を「ピンポ ン」と表現したつもりでした。ちなみに田中さんの sync-each.rb での、 > t = t.resume x の t = は、不用ですね。私の最初の sync-each.rb の > a = @ct.pass(a) の a = はぐるぐる回しにこだわったために必要だったので。 >スレッド間(コルーチン間)の遷移のインターフェースはどうすべきか悩んだん >ですが、明示的に続きのコルーチンを指定させても行数が増えなかったので、 >記述が繁雑にならないかぎりにおいて低レベルな方が便利、ということで明示 >的に続きを指定させることにしました。 それはそういうことだと思っていました。ただ、CoThread でも1つあ たり1回しか fork しないなら、コルーチン相当になるので、低レベル での記述力が落ちているわけではないです。その例が sync-each2.rb でして。 >CoThread のように暗黙に決まるというのもありだとは思いますが、どんな場 >合に、明示的な指定よりも楽になるでしょう? あ、わかりました。CoThread という名前が紛らわしい!これは Thread ではなく、ThreadGroup に近いのかもしれません。 この順番を暗黙に決める方法は Tosh さんのアイデアなんですが、多 分 round robin というのが念頭にあったんだと思います。これがあ るから、前の CoQueue(あるいは後に添付する CoSizedQueue)が手 軽に書けるのだと思います。また、次のような enumerable オブジェ クトの「マージ」もシンプルです。 -----^ merge-each.rb require "co-thread" class MergeEach include Enumerable def initialize(*es) @ct = CoThread.new @es = es end def each @es.each do |e| @ct.fork do e.each do |x| yield x @ct.pass end end end @ct.exit end end if $0 == __FILE__ class Tosa def initialize(a, d) @a, @d = a, d end def each sum = @a loop do yield sum sum += @d end end end n = 3 ts = (0...n).map{|i| Tosa.new(i, n)} MergeEach.new(*ts).each do |x| break if x > 20 p x end end -----$ merge-each.rb CoQueue を使った MergeEach なら次のようになります。 -----^ merge-each-q.rb require "co-queue" class MergeEach include Enumerable def initialize(*es) ct = CoThread.new @cq = CoQueue.new(ct) es.each do |e| ct.fork do e.each do |x| @cq.enq(x) end end end end def each loop do yield(@cq.deq) end end end -----$ merge-each-q.rb >まぁ、Thread.critical を使って thread.rb を書くのと、callcc を使って >co-thread.rb を書くのとどちらが難しいかというと... どっちもどっちじゃ >ないですかね。 そうかなあ、Thread の方が難しいという印象があるけどなあ。 >> しかし、Thread と Continuation、どちらでも書けるなら前者でなければ >> ならない、という理由もない気もします。 > >残念ながら、後者でなければなならない、という理由もないので、ならばどち >らが適切か、というのが問題ですかねぇ。 それはそうですね。同じものを Thread と Continuation で作ると、 Continuation の方が何倍も遅い、という問題もあるし。 付録: -----^ co-sized-queue.rb require "co-thread" class CoSizedQueue def initialize(ct, max = 1) @thread = ct @max = max @queue = [] end def enq(x) @thread.pass while @queue.size >= @max @queue.push(x) @thread.pass end def deq @thread.pass while @queue.empty? @queue.shift end end if $0 == __FILE__ ct = CoThread.new qu = CoSizedQueue.new(ct, 2) ct.fork do [0, 2, 4].each do |x| qu.enq(x) end end ct.fork do [1, 3, 5, 6, 8, 10].each do |x| qu.enq(x) end end ct.fork do [7, 9].each do |x| qu.enq(x) end qu.enq :eoi end while :eoi != (x = qu.deq) p x end end -----$ co-sized-queue.rb