無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

Mutex

Thread間の処理の同期にはMutexを使います。

  • Mutex#synchronizeで
    1. Mutexのロックの獲得
    2. ブロックの処理を実行。
    3. ロックの解除

を行います。Javaのsynchronizedブロックと似た感じで使えます。

require 'thread'

m = Mutex.new
ts = []
3.times { |j|
  ts << Thread.start {
    m.synchronize { # ブロック内の処理が同期実行される。
      5.times { |i|
        puts "thread-" << j.to_s << " : " << i.to_s
        sleep rand * 0.1
      }
    }
  }
}

ts.each {|t| t.join }

実行結果です。ブロック内の処理が同期実行されています。

thread-0 : 0
thread-0 : 1
thread-0 : 2
thread-0 : 3
thread-0 : 4
thread-1 : 0
thread-1 : 1
thread-1 : 2
thread-1 : 3
thread-1 : 4
thread-2 : 0
thread-2 : 1
thread-2 : 2
thread-2 : 3
thread-2 : 4

ちなみに、同期実行しない場合次のようになります。

require 'thread'

m = Mutex.new

ts = []
3.times { |j|
  ts << Thread.start {
    # m.synchronize { # 同期しない
      5.times { |i|
        puts "thread-" << j.to_s << " : " << i.to_s
        sleep rand * 0.1
      }
    # }
  }
}

ts.each {|t| t.join }
thread-0 : 0
thread-1 : 0
thread-2 : 0
thread-0 : 1
thread-2 : 1
thread-1 : 1
thread-2 : 2
thread-0 : 2
thread-2 : 3
thread-1 : 2
thread-0 : 3
thread-2 : 4
thread-1 : 3
thread-0 : 4
thread-1 : 4

明示的なロック

Mutex#lock、Mutex#try_lockで明示的にロックを取得することもできます。

  • 獲得したロックはMutex#unlockで解放します
    • begin ensure で必ずunlockすること!
  • 別の誰かによってMutexが既にロックされていた場合
    • Mutex#lock .. ロックが解放されるまで処理がブロックされます
    • Mutex#try_lock .. ロックは行われず、falseが返されます

以下はlockを使った同期化のサンプルです。

require 'thread'

m = Mutex.new

ts = []
3.times { |j|
  ts << Thread.start {
    begin
      m.lock # ロック
      5.times { |i|
        puts "thread-" << j.to_s << " : " << i.to_s
        sleep rand * 0.1
      }
    ensure
      m.unlock
    end
  }
}

ts.each {|t| t.join }

実行結果です。synchronizedと同じく同期実行されています。

thread-0 : 0
thread-0 : 1
thread-0 : 2
thread-0 : 3
thread-0 : 4
thread-1 : 0
thread-1 : 1
thread-1 : 2
thread-1 : 3
thread-1 : 4
thread-2 : 0
thread-2 : 1
thread-2 : 2
thread-2 : 3
thread-2 : 4

try_lockを使ってみます。すでに誰かにロックされていた場合、例外がスローされます。

m = Mutex.new

ts = []
3.times { |j|
  ts << Thread.start {
    locked = false
    begin
      locked = m.try_lock # ロック
      raise "lock failed." << j.to_s unless locked
      5.times { |i|
        puts "thread-" << j.to_s << " : " << i.to_s
        sleep rand * 0.1
      }
    ensure
      m.unlock if locked # ロック解除
    end
  }
}

ts.each {|t|
  begin
    t.join
  rescue
    puts $!
  end
}

実行結果です。2つ失敗しています。

thread-0 : 0
thread-0 : 1
thread-0 : 2
thread-0 : 3
thread-0 : 4
lock failed.1
lock failed.2

注意:2重ロックは許可されない。

注意事項として、ロックされたMutexを再度ロックすると、たとえ同じスレッドであってもエラーになります。

require 'thread'

m = Mutex.new

p m.lock # ロック

# ロックしたものを再度ロック → ロックできない。
p m.try_lock
p m.lock

実行結果です。

#<Mutex:0x100f7e00 @locked=true, @waiting=[]>
false
xxx/thread.rb:100:in `stop': stopping only thread (ThreadError)
    note: use sleep to stop forever	from xxx/thread.rb:100:in `lock'
    from xxx/w_lock.rb:10

Mutexの代わりにMonitorを使うと、同一スレッドから複数回ロックすることができます。

require 'monitor'

m = Monitor.new

m.enter # ロック

# ロックしたものを再度ロック → ロックできる
p m.mon_try_enter # true
m.enter

ただし、Monitorの場合、ロックしたスレッド自身でしかロックを解放できません。

m = Monitor.new
m.enter
t = Thread.new {
  m.exit
}
t.join
xxx/monitor.rb:259:in `mon_check_owner': current thread not owner (ThreadError)
    from xxx/w_monitor.rb:18:in `join'
    from xxx/w_monitor.rb:18