原です。

三たび Condition Variable についてです。

In message "[ruby-list:14847] Re: ConditionVariable (again)"
    on 99/06/07, Shugo Maeda <shugo / netlab.co.jp> writes:

|前田です。

|防ぐ手はある(フラグを一個追加すればいいのかな)と思うのですが、そ

cv2.rb に手を加えて、別の方法でこれをやってみました。

-------------^ cv3.rb
class Mutex
  attr_writer :signal
  
  def initialize
    @waiting = [[], []]
    def @waiting.push0(x); self[0].push(x); end
    def @waiting.push(x); self[1].push(x); end
    def @waiting.shift; self[0].shift || self[1].shift; end

    @locked = nil
    @signal = nil
  end

  def locked?
    @locked
  end

  def try_lock
    result = false
    Thread.critical = true
    unless @locked || @signal && @signal != Thread.current

      @signal = nil
      if iterator?
	yield
      else
	@locked = true
      end

      result = true
    end
    Thread.critical = false
    result
  end

  def lock(n = nil)
    Thread.critical = true
    while @locked || @signal && @signal != Thread.current
      if n
	@waiting.push0 Thread.current
      else
	@waiting.push Thread.current
      end	
      Thread.stop
      Thread.critical = true
    end

    @signal = nil
    if iterator?
      yield
    else
      @locked = true
    end

    Thread.critical = false
    self
  end

  def unlock
    return unless @locked
    Thread.critical = true
    t = @waiting.shift

    if iterator?
      yield
    else
      @locked = nil
    end

    Thread.critical = false
    t.run if t
    self
  end

  def synchronize
    return yield if @locked == Thread.current
    begin
      owner = nil
      lock do
	owner = @locked
	@locked = Thread.current
      end

      yield

    ensure
      unlock do
	@locked = owner
      end
    end
  end

  def wait
    owner = nil
    if @locked
      Thread.critical = true
      t = @waiting.shift
      t.wakeup if t

      owner = @locked
      @locked = nil

      yield

      Thread.stop
    end
    lock(0) do
      @locked = owner
    end
  end
end

class ConditionVariable
  def initialize
    @waiters = []
  end

  def wait(mutex)
    mutex.wait do
      @waiters.push(Thread.current)
    end
  end

  def signal(mutex = nil)
    if t = @waiters.shift
      if mutex
	mutex.signal = t
	t.wakeup
      else
	t.run
      end
    end
  end

  def broadcast
    Thread.critical = true
    waiters0 = @waiters.dup
    @waiters.clear
    Thread.critical = false

    for t in waiters0
      t.wakeup
    end
  end
end
-------------$ cv3.rb

これを走らせてみます。テストプログラムは [ruby-list:14819] の
test1.rb を改造した test-cv.rb(後掲)で、バッファサイズ3のパ
イプに、送り手4スレッド受け手3スレッドでオブジェクトを通すも
のです。停止するのは全ての受け手スレッドが 1000 個のオブジェク
トを受け取った時とします。

結果は

blade$ time ./test-cv.rb cv3
TEST CONDITION VARIABLE <<< CV3 >>>
Sender:4, Receiver:3 (each receives 1000 times), Buffer:3
Idling1: send = 0, receive = 0
Idling2: send = 0, receive = 0
#1 sends 656 times
#2 sends 637 times
#3 sends 994 times
#4 sends 716 times

real    0m7.074s
user    0m6.780s
sys     0m0.010s

Idling1 は送出(send)、受入(receive)において無駄な wait 動作に
何回「入った」かを表し、Idling2 は無駄に wait ループをトータル
で何回「回した」かを表しています。

その下は各スレッドが幾つオブジェクトを送出したかを表しています。
(#3 が大きいのは謎。)

cv3.rb の構造ですが、シグナルに宛先をつけて送り、他のスレッド
はシグナルが自分向けでなければ Mutex がロックされてなくてもブ
ロックさせます。従って Mutex にシグナルの宛先であるスレッドを
覚えておくインスタンス変数 @signal が必要になっています。

また、ConditionVariable#signal() に mutex を渡さなければ一般的
な CV として動く様にしました。

cv3 では Idling1/2 が 0 です。これは signal の後、他のスレッド
に変数を操作されることなく、確実に wait に処理が回るので、wait 
において while などで条件の再チェックが実は不必要である事を示し
ています(if で調べれば十分という事)。ある意味で究極の CV ??


monitor.rb を参考にせず、独立に書いていたのですが、結局どん
どん monitor.rb に似て来て最終的に、、、酷似している。(^^;
結局誰が書いても似た感じになるのかな。

参考までに cv2.rb と monitor.rb と mutex.c ([ruby-list:14849])
での結果です。

blade$ time ./test-cv.rb cv2
TEST CONDITION VARIABLE <<< CV2 >>>
Sender:4, Receiver:3 (each receives 1000 times), Buffer:3
Idling1: send =84, receive =10
Idling2: send =945, receive =10
#1 sends 80 times
#2 sends 803 times
#3 sends 935 times
#4 sends 1185 times

real    0m8.963s
user    0m8.790s
sys     0m0.000s

blade$ time ./test-cv.rb monitor
TEST CONDITION VARIABLE <<< MONITOR >>>
Sender:4, Receiver:3 (each receives 1000 times), Buffer:3
Idling1: send =35, receive = 0
Idling2: send =35, receive = 0
#1 sends 1263 times
#2 sends 624 times
#3 sends 483 times
#4 sends 632 times

real    0m11.545s
user    0m11.380s
sys     0m0.020s

blade$ time ./test-cv.rb mutex
TEST CONDITION VARIABLE <<< MUTEX >>>
Sender:4, Receiver:3 (each receives 1000 times), Buffer:3
Idling1: send = 9, receive = 0
Idling2: send = 9, receive = 0
#1 sends 676 times
#2 sends 675 times
#3 sends 952 times
#4 sends 700 times

real    0m5.479s
user    0m5.200s
sys     0m0.010s


何で一般的には Conditional Variable は、cv3.rb の様な仕様
になっていないんでしょうね。Mutex の拡張が必要だからか効率
が悪くなりそうだからか複雑だからか、、。