dak ブログ

python、rubyなどのプログラミング、MySQL、サーバーの設定などの備忘録。レゴの写真も。

rubyでflockを使った排他制御

2011-04-16 21:30:01 | ruby
rubyでプロセス間で排他制御を行うのに、一番最初に思いつくのはロックファイルを作成する方法でしょう。
ロックファイルを使う方法では、ロックファイルがある場合にはsleepして、再びロックファイルの存在をチェックし、ロックファイルがない場合にはロックファイルを作成します。

しかし、ロックファイルの作成後にエラーが発生して、ロックファイルはあってもロックをかけたプロセスが異常終了した場合には、ロックファイルが残ったままになって、他のプロセスがロックを獲得できなくなってしまいます。
ロックファイルにプロセスIDを書き込んでおけば、ロックファイルに記述されたプロセスIDが存在しないことをチェックできるので、エラー処理に役立ちますが、少し面倒ではないでしょうか。

File#flockを使って排他制御を行うと、以下の理由でプログラムが少し楽になるでしょうか。
・ruby インタプリタが終了した場合にロックが解除されるため、ロックファイルを削除する必要がない。
・排他ロックを実行すると、ロックが解除されるまでプロセスが待機状態になり、ロックが解除されればロックを獲得できます。
sleepして、再度ロックファイルをチェックするような処理は不要です。

■プログラム

#!/usr/local/bin/ruby
#
# flock による排他制御
#
# usage: flock.rb {ID}

$KCODE = 'u'
require 'jcode'
require 'time'

# 指定ファイルでロック
# @param lockfile
# @return lockfileのstream / nil
def lock(lockfile)
st = File.open(lockfile, 'a')
if ! st
STDERR.print("failed to open\n")
return nil
end

begin
st.flock(File::LOCK_EX)
rescue
STDERR.print("failed to lock\n")
return nil
end

return st
end

# アンロック
# @param st
# @return true / false
def unlock(st)
begin
st.flock(File::LOCK_UN)
st.close
rescue
STDERR.print("failed to unlock")
return false
end

return true
end

sleep(5)

# ロック
lock_st = lock('lockfile')

# 排他制御
print("[#{Time.now().to_s}] get lock [#{ARGV[0]}]\n")
sleep(10)
print("[#{Time.now().to_s}] release lock [#{ARGV[0]}]\n")

# アンロック
unlock(lock_st)

■実行結果
$ ./flock.rb 1&
$ ./flock.rb 2&
$ ./flock.rb 3&
[Sat Apr 16 21:26:33 +0900 2011] get lock [1]
[Sat Apr 16 21:26:43 +0900 2011] release lock [1]
[Sat Apr 16 21:26:43 +0900 2011] get lock [2]
[Sat Apr 16 21:26:53 +0900 2011] release lock [2]
[Sat Apr 16 21:26:53 +0900 2011] get lock [3]
[Sat Apr 16 21:27:03 +0900 2011] release lock [3]