Rubyで結城浩せんせいのJavaマルチスレッド本 7# Producer-and-Consumer
Producer-and-Consumer
回転寿司だと思えば楽。
作る人、食べる人、カウンターテーブル。が別々に仕事をする。
- 作るひとはどんどん作り
- 食べる人がどんどん食べる。
- テーブルにお寿司が無くなったら、食べるのを休憩する
- テーブルが一杯になった、握るのを中断する。
これはカウンタテーブルに置けるお皿の数に上限があるから良い。上限がなかったら際限なく作るので無駄。
Rubyにはパッケージにクラスがある。この上限があるQueueをSizedQueueと言う名前で使える。
Producer-and-Consumer(SizedQueueを使った場合)
#!/usr/bin/env ruby #Producer and Consumer # SizedQueue を使った例。(とても簡単) require 'thread' class EaterThread < Thread def initialize name, table @name = name @table = table super{ while true do cake = @table.take puts @name + " takes "+ cake sleep rand(3) end } end end class MakerThread < Thread @@id = 0 @@m = Mutex.new attr_reader :name def initialize name, table @name = name @table = table super{ while true do cake = "[Cake No. #{MakerThread.nextId} by #{self.name}]" @table.put cake puts @name + " puts "+ cake sleep rand(3) end } end def MakerThread.nextId @@m.synchronize{ @@id = @@id + 1 } @@id end end class Table < SizedQueue def put cake self.push cake end def take cake = self.pop cake end end table = Table.new 10 threads = [ (1..3).map{|i| MakerThread.new( "MakerThread #{i.to_s}", table ) }, (1..3).map{|i| EaterThread.new( "EaterThread #{i.to_s}", table ) }, ].flatten threads.each{|t| t.join } puts "END"
とても簡単。
だけどSizedQueueを作ることがこの章の目的。なので、、、SizedQueueを使うと勉強にならない。
Table(SizedQueue)をちゃんと作ったProducer-and-Consumer
#!/usr/bin/env ruby #Producer and Consumer # Tableを自分で実装する。 require 'thread' class EaterThread < Thread def initialize name, table @name = name @table = table super{ while true do #puts @name + " wants to take " cake = @table.take puts @name + " takes "+ cake sleep rand(3) end } end end class MakerThread < Thread @@id = 0 @@m = Mutex.new attr_reader :name def initialize name, table @name = name @table = table super{ while true do #puts @name + " wants to put " cake = "[Cake No. #{MakerThread.nextId} by #{self.name}]" @table.put cake puts @name + " puts "+ cake sleep rand(3) end } end def MakerThread.nextId @@m.synchronize{ @@id = @@id + 1 } @@id end end class Table def initialize size raise "size should be > 0 " if size < 1 @size = size @list = Array.new @m = Mutex.new @cv_full = ConditionVariable.new @cv_empty = ConditionVariable.new end def put cake @m.synchronize{ while( @size == @list.size) do #puts "waiting : table is full" @cv_full.wait(@m) end @list.push cake @cv_empty.broadcast } end def take @m.synchronize { while(@list.empty?) do #puts "waiting : table is empty" @cv_empty.wait(@m) end cake = @list.shift @cv_full.broadcast cake } end end table = Table.new 3 threads = [ (1..3).map{|i| EaterThread.new( "EaterThread #{i.to_s}", table ) }, (1..3).map{|i| MakerThread.new( "MakerThread #{i.to_s}", table ) }, ].flatten threads.each{|t| t.join }#Clientスレッド待ち puts "END"
ちょっとめんどくさい
スレッドを待たせる箇所が二つ
@cv_full = ConditionVariable.new # テーブルが一杯で置けないスレッド @cv_empty = ConditionVariable.new # テーブルが空っぽで取れないスレッド
Guarded Suspensionが2箇所出てきた。Rubyの場合オブジェクトのWAITセットが無いので、conditionVariableでWaitする。