原です。
三たび 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 の拡張が必要だからか効率
が悪くなりそうだからか複雑だからか、、。