それマグで!

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

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

Rubyでマルチスレッドその3 Single Thread Execution

結城せんせいの「マルチスレッド本」を参考書にし、これをRubyで解釈する。
Rubyデザインパターンも両方覚えちゃおうという強欲な試み。

Single Threaded Execution

壊れてしまう例
#!/usr/bin/env ruby
#Single Threaded Execution
#これは壊れる例
require "thread"
class Gate
  def initialize
    @counter = 0
  end
  def go_through name , address
      @name = name 
      @address = address
      @counter = @counter + 1
      self.check
  end
  def check 
    if @name != @address then
      puts "BROKEN ** " + self.to_s
    end
  end
  def to_s
    return "No. #{@counter} : #{@name} , #{@address} "
  end
end

gate = Gate.new

class UserThread < Thread
  def initialize(myname,myaddress,gate)
    @myaddress = myaddress
    @myname = myname
    @gate = gate
    block = Proc.new{
      while(true) do
        #selfの@参照は、このインスタンス
        @gate.go_through @myname, @myaddress
      end
    }
    super(&block)
  end
end
threads = ("A".."C").map{|i|
  UserThread.new(i,i,gate)
}
threads.each{|t| t.join}
puts "end"

これを実行すると壊れる。

$ > ruby thread03-1.rb
BROKEN ** No. 206 : B , A
BROKEN ** No. 232 : A , C
BROKEN ** No. 1044 : A , C
BROKEN ** No. 1148 : A , C
BROKEN ** No. 1174 : C , B

コレは壊れない例。

#!/usr/bin/env ruby

#Single Threaded Execution
#これは壊れない例
require "thread"

class Gate
  def initialize
    @m = Mutex.new
    @counter = 0
  end
  def go_through name , address
    @m.synchronize{ #ロックを取る
      @name = name 
      @address = address
      @counter = @counter + 1
      self.check
    } #ロックを解除する。
  end
  def check 
    if @name != @address then
      puts "BROKEN ** " + self.to_s
    end
  end
  def to_s
    return "No. #{@counter} : #{@name} , #{@address} "
  end
end
gate = Gate.new

class UserThread < Thread
  def initialize(myname,myaddress,gate)
    @myaddress = myaddress
    @myname = myname
    @gate = gate
    block = Proc.new{
      while(true) do
        #selfの@参照は、このインスタンス
        @gate.go_through @myname, @myaddress
      end
    }
    super(&block)
  end
end

threads = ("A".."C").map{|i|
  UserThread.new(i,i,gate)
}
threads.each{|t| t.join}
puts "end"
これを実行すると壊れない。
$ > ruby thread03-1.rb
                            #ナニも起きない。Ctrl+Cで終了

Rubyにはsynchronizeが無い

替わりにMutexが用意されています。

Mutexは結城浩先生のデザパタ本に練習問題として出てきます。

Mutexについて

lock
 
mutex オブジェクトをロックします。一度に一つのス
レッドだけが mutex をロックできます。既にロックさ
れている mutex に対してロックを行おうとしたスレッド
は mutex のロックが解放されるまで、実行が停止されます。

mutextの使用例

Javaのsyncの替わりにmutex単位でロックを取ります。Javaはオブジェクトを守ってSynchronizeする。RubyはMutex単位になる。Javaの様にObjectのWaitで実行待ちをすることもない。この例だと、Mutex単位で待つ

  def go_through name, address
    @m.synchronize{ #ロックを取る
      @name = name 
      @address = address
      @counter = @counter + 1
      self.check
    } #ロックを解除する。

Threadの継承

Javaのようなrun/startで管理されないRubyのスレッド。スレッドの動作させるにはブロックを作ってそこを実行する。

block=Proc.new{  do_something }
t=Thread.new(&block)

結城浩せんせいのJavaマルチスレッド本に合わせて、敢えて、スレッドを継承して書いています。僕は次のようなパターンで書くことにしました。

thread継承の例:javaのコードを書き起こすとき

class MyThread < Thread
  def initialize
    block=Proc.new{  do_something }
    super(&block)
  end
end

thread=MyThread.new()

startを使う例:javaのコードを書き起こすとき

class MyThread
  def start
    block=Proc.new{  do_something }
    Thread.new(&block)
  end
end
thread=MyThread.new().start