File#flockを利用したReadWriteロック
File#flockを利用したReadWriteロックを書いてみました。仕様は以下の通りで、
- 「読み中に書き」
- 「書き中に書き」
- 「書き中に読み」
の場合、前の処理が終了してロックを解放するまで次の処理をブロックすることで、処理を排他制御します。
読み中に.. | 書き中に.. | |
読み | ×排他制御されない | ○排他制御される |
書き | ○ | ○ |
ReadWriteロック:
require "fileutils" # File.flockを利用したRead-Writeロック class FileLock def initialize(lockfile) @lockfile = lockfile end # 読み込みロックしてブロックを実行する。 # -ロックされていても他の読み込みユーザーはブロックさない。 # -他の書き込みユーザーはブロックされる。 def readlock ( &block ) FileUtils.touch( @lockfile ) unless File.exist?(@lockfile) File.open( @lockfile, "r" ) { |f| f.flock(File::LOCK_SH) begin block.call ensure f.flock(File::LOCK_UN) end } end # 書き込みロックしてブロックを実行する。 # -ロックされている場合、他の読み込みユーザー/書き込みユーザー共にブロックされる。 def writelock ( &block ) FileUtils.touch( @lockfile ) unless File.exist?(@lockfile) File.open( @lockfile, "w" ) { |f| f.flock(File::LOCK_EX) begin block.call ensure f.flock(File::LOCK_UN) end } end end
使い方&動作確認:
# ロックしない。 # 排他制御されないため、a,b,cがバラバラに出力される。 puts "ロックしない ---" ["a","b","c"].each {|name| fork { 3.times{|i| puts name + "-" + i.to_s; sleep rand } } } sleep 5 # 書き込みロックで排他制御 # a,b,cでまとまって出力される。どれが最初に来るかは不定。 puts "\n書き込みロックで排他制御 ---" lock = FileLock.new("./tmp.lock") ["a","b","c"].each {|name| fork { lock.writelock { 3.times{|i| puts name + "-" + i.to_s; sleep rand } } } } sleep 5 # 読み込みロックのみ # 排他制御されないため、a,b,cがバラバラに出力される。 puts "\n読み込みロック ---" lock = FileLock.new("./tmp.lock") ["a","b","c"].each {|name| fork { lock.readlock { 3.times{|i| puts name + "-" + i.to_s; sleep rand } } } } sleep 5 # 読み書きのロックが混在 # -読み同士は排他制御されないため、a,bがバラバラに出力される。 # -読みと書きは排他制御されるため、c,(a+b)はまとまって出力される。 puts "\n読み書き混在 ---" lock = FileLock.new("./tmp.lock") ["a","b"].each {|name| fork { lock.readlock { 3.times{|i| puts name + "-" + i.to_s; sleep rand } } } } ["c"].each {|name| fork { lock.writelock { 3.times{|i| puts name + "-" + i.to_s; sleep rand } } } } sleep 5
実行結果は以下。期待通り動作している感じです。
ロックしない --- a-0 b-0 c-0 b-1 a-1 b-2 c-1 a-2 c-2 書き込みロックで排他制御 --- b-0 b-1 b-2 a-0 a-1 a-2 c-0 c-1 c-2 読み込みロック --- a-0 b-0 c-0 a-1 b-1 c-1 b-2 a-2 c-2 読み書き混在 --- b-0 a-0 a-1 b-1 a-2 b-2 c-0 c-1 c-2