それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Rubyでマルチスレッド その7# Producer-and-Consumer

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する。