Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

マルチスレッドデザインパターン | Read-Write Lockパターン

概要

Read-Write Lockパターン

詳細

スレッドの処理に置いて読むことと書くことの意味は異なります。
・読み込み中に他のスレッドが読み込みをしても問題ない
・読み込み中に他のスレッドが書き込みすると本来読み込みたい内容と異なるため問題がある
・書き込み中は他のスレッドは書き込み不可

排他制御はパフォーマンスを落とすが、読み込み中-読み込み中時をロックを対象外にすることで
パフォーマンスを上げることが出来ます。

構成

Reader:読み取り
Writer:書き込み
SharedResource:読み書き双方で利用している共有リソース。読み取り用と書き込み用のメソッドを持つ
ReadWriteLock:読み書きロック管理用クラス。読み書き別々にロック状態を保持する。

サンプル

仕様

あるデータベースを想定します。
あるテーブルの読み込み中に別のスレッドがテーブルを読み込んでも問題有りません。
あるテーブルの読み込み中に別のスレッドがテーブルを書き込んだ場合で、
そのテーブルが更新処理用に読み込んだ場合、本来の更新意図と異なってしまうため問題があります。
あるテーブルの書き込み中に他スレッドから書き込みされることは問題があります。
※単純化のためにレコードではなくテーブル単位で考えます

構成

Reader:DbReader
Writer:DbWriter
SharedResource:DataBase
ReadWriteLock:Rubyの標準ライブラリSync_m
※自作で作成した場合は、ReadLockは読み込み開始時に書き込み中の処理がなければロックを取得。
ロックを取得出来るまで待機します。ロック取得時は読み込み中のスレッド数をインクリメントします。
読み込みが終了したらアンロック処理を行い、読み込み中スレッド数をデクリメントして、スレッドに通知を行います。
WriteLockは読み書き中のスレッドが1件もなければロックを取得。
ロックを取得できるまで待機します。ロック取得時は書き込み中のスレッド数をインクリメントします。
書き込みが終了したらアンロック処理を行い、書き込み中スレッド数をデクリメントして、スレッドに通知を行います。

ソースコード
# encoding: utf-8
require "pp"
require "thread"
require "sync"

def random_sleep
  sleep ((rand 10) + 1) * 0.05
end

class Table
  include Sync_m

  def initialize
    super
    @contents = ""
  end

  def read(name, count)
    sync_synchronize(:SH) {
      sleep 0.2
      puts "read:#{name}:#{count}:#{@contents}"
      return @contents
    }
  end

  def write(value, count)
    sync_synchronize(:EX) {
      sleep 0.1
      puts "write:#{count}:#{value}"
      @contents += value
    }
  end
end

class DbReader < Thread
  def initialize(name, table)
    block = lambda {
      (1..5).each do |i|
        random_sleep
        ret = "#{table.read name, i}"
      end
    }
    super(&block)
  end
end

class DbWriter < Thread
  def initialize(name, table)
    block = lambda {
      (1..5).each do |i|
        sleep 0.01
        table.write("(#{name}:#{i}),", i)
      end
    }
    super(&block)
  end
end

table = Table.new
threads = []
threads << DbReader.new("雪だるま", table)
threads << DbWriter.new("もぐら", table)
threads << DbReader.new("ロブスター", table)
threads << DbWriter.new("操り人形",table)
threads << DbReader.new("トカゲ", table)
threads << DbWriter.new("人魚", table)
threads << DbReader.new("吸血鬼", table)
threads << DbWriter.new("ハーピー ", table)
threads << DbReader.new("妖精", table)
threads << DbWriter.new("サラマンダー", table)
threads.each {|t|t.join}
実行結果例
write:1:(ハーピー :1),
read:トカゲ:1:(ハーピー :1),
read:吸血鬼:1:(ハーピー :1),
write:1:(人魚:1),
write:1:(サラマンダー:1),
write:2:(ハーピー :2),
write:2:(サラマンダー:2),
write:1:(もぐら:1),
read:ロブスター:1:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),
read:雪だるま:1:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),
read:トカゲ:2:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),
read:ロブスター:2:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),
read:吸血鬼:2:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),
read:妖精:1:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),
read:雪だるま:2:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),
write:3:(ハーピー :3),
read:吸血鬼:3:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),
read:トカゲ:3:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),
read:ロブスター:3:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),
read:妖精:2:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),
read:雪だるま:3:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),
read:吸血鬼:4:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),
read:トカゲ:4:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),
read:妖精:3:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),
write:2:(人魚:2),
write:2:(もぐら:2),
write:1:(操り人形:1),
write:3:(人魚:3),
write:2:(操り人形:2),
read:妖精:4:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),(人魚:2),(もぐら:2),(操り人形:1),(人魚:3),(操り人形:2),
read:トカゲ:5:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),(人魚:2),(もぐら:2),(操り人形:1),(人魚:3),(操り人形:2),
read:ロブスター:4:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),(人魚:2),(もぐら:2),(操り人形:1),(人魚:3),(操り人形:2),
read:雪だるま:4:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),(人魚:2),(もぐら:2),(操り人形:1),(人魚:3),(操り人形:2),
read:ロブスター:5:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),(人魚:2),(もぐら:2),(操り人形:1),(人魚:3),(操り人形:2),
read:妖精:5:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),(人魚:2),(もぐら:2),(操り人形:1),(人魚:3),(操り人形:2),
read:吸血鬼:5:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),(人魚:2),(もぐら:2),(操り人形:1),(人魚:3),(操り人形:2),
write:4:(人魚:4),
write:3:(操り人形:3),
write:3:(サラマンダー:3),
write:4:(ハーピー :4),
write:3:(もぐら:3),
write:5:(人魚:5),
write:4:(もぐら:4),
write:5:(ハーピー :5),
write:4:(サラマンダー:4),
read:雪だるま:5:(ハーピー :1),(人魚:1),(サラマンダー:1),(ハーピー :2),(サラマンダー:2),(もぐら:1),(ハーピー :3),(人魚:2),(もぐら:2),(操り人形:1),(人魚:3),(操り人形:2),(人魚:4),(操り人形:3),(サラマンダー:3),(ハーピー :4),(もぐら:3),(人魚:5),(もぐら:4),(ハーピー :5),(サラマンダー:4),
write:4:(操り人形:4),
write:5:(もぐら:5),
write:5:(操り人形:5),
write:5:(サラマンダー:5),