複数のプログラムが1つのファイルに同時に書き込みを行なうと 整合性が取れなくなる。たとえば,あるファイルに書かれている数値を 1だけ増やすプログラムを2つ走らせたとする。最初,ファイルの内容が
10
だったとして,1増やすプログラムを2回動かすと中味は12になる はずである。しかしほぼ同時にプログラムを動かすと 以下のようになる。
時刻 | プログラムその1の流れ↓ | プログラムその2の流れ↓ |
---|---|---|
0ms | 起動開始 | |
10ms | 元ファイルを開く | 起動開始 |
20ms | 10を読み取る | 元ファイルを開く |
30ms | 変数に1を足す | 10を読み取る |
40ms | ファイルに書き込む | 変数に1を足す |
50ms | 終了 | ファイルに書き込む |
60ms | 終了 |
結果は11になる。
このように,あるファイルの中味を読み取りなんらかの修正を加える プログラムは,同じプログラムが同時進行している可能性を考慮し,
のような注意を払う必要がある。
同時に2つのプロセスが同じ処理を行なわないようにすることを 排他処理 という。ファイル修正の排他処理には ファイルロックが用いられる。
Rubyでは
Fileクラス
の flock
メソッドでロックを行なう。
まず,ファイルロックのないまずい処理例を示す。先に用いた
dbm-ops.rb
プログラムを
同時に動かす実験をしてみる。
ktermその1
./dbm-ops.rb -a
あいうえお,12345
[C-d]
ktermその2
./dbm-ops.rb -a
かきくけこ,abcde
[C-d]
両プログラムを起動してから, 各ウィンドウでデータ入力を行ないC-d をタイプする。 すると,あとで終わらせた方のデータしか追加されていないことが分かる。
ファイルロック処理を追加してみる。ロックの必要な処理の前に ロック設定処理を,後ろにロック解除処理を入れる。
lockfile="ロックファイルの名前" open(lockfile, "w") do |lf| lf.flock(File::LOCK_EX) # ロック設定 DBM.open(datafile) do |x| : end lf.flock(File::LOCK_UN) # ロック解除 end
ktermその1
先に起動する
./dbm-flock.rb -a
./database operation start
いろはにほへと,12345
[C-d]
ktermその2
その1を終わらせる前に起動する
./dbm-flock.rb -a
あたらしい朝,いえーい
[C-d]
./database operation start
(その1終了後処理が始まる)
このプログラムではflockを行なうファイルとしてダミーファイルを openしたが,これは排他処理を行なう必要のあるファイル形式がdbmだからで, 普通にopenして修正を行なうファイルの排他処理を行なうなら, そのファイルをopenしたオブジェクトでflockすればよい。
PStore
はtransactionブロックそのものが排他処理を
行なう。
先述のとおりflock処理は極力短くする。さもなくばロック解除待ちの
プロセスが溢れかえる。dbm-flock.rb
は,新規データの入力処理もロック内で行なっているので,入力に
時間がかかる場合でもずっとロックがかかったままとなる。データベース
更新処理のみをロックするように書き換える。データを追加する部分だけを
抜き出したプログラム
dbm-add-flock.rb
を示す。
#!/usr/bin/env ruby require 'dbm' # dbmクラスを利用 datafile = "./database" # データベースファイル名 lockfile = datafile+".lck" # ロックファイル名 data = Hash.new while csv=gets # 先にCSVデータを読んでハッシュに代入 if /([^,]*),(.*)/ =~ csv data[$1] = $2 end end open(lockfile, "w") do |lf| lf.flock(File::LOCK_EX) DBM.open(datafile) do |x| STDERR.puts "#{datafile} update starts." for key, val in data x[key] = val end sleep 2 # 本当は一瞬で終わるが少し時間を掛ける STDERR.puts "#{datafile} update ends." end lf.flock(File::LOCK_UN) end
排他処理の必要なファイルを修正する場合は File::LOCK_EX
(排他ロック)を指定するが,読み取りのみの場合は
File::LOCK_SH
(共有ロック) を指定する。
複数のプロセスが互いに別プロセスのロック解除を待ち続けて 永遠に解除が起きない状態を デッドロック という。
たとえば,2つのファイル A と B があり,それぞれに 数値が書かれていたとする。このとき
という2つのプロセスがほぼ同時に走ったとする。プロセス1は 「AをロックしてからBをロック」し。プロセス2は 「BをロックしてからAをロック」しようとしたとする。もし, 「プロセス1がAをだけをロック,プロセス2がBだけをロック」した状態に なったら,この状態は永遠に解除されない。
この例の場合,デッドロックを回避するには複数の資源をロックする順位を 決めておく。AとBであれば,A→Bという順番でのみロックするように 設計する。
一般的には,ロックすべき資源は多数あるのでロックを完ぺきに解除するの は難しい。全体的に可能性を極力下げるよう設計する。
これらを注意するのと同時に,デッドロック回避が本質的に 難しいことを考慮して,
などをしておく。