Bashでロックを取る方法
前回に続いてBashネタで、Bashで排他ロックを取る方法について。
並行プログラミングをしようとするならば、排他は必要となる。シェルスクリプトでも並行性について考慮しなければならない場面があるが、アトミックにロックを取る手段はシェルスクリプトだけでは無理で、OSの機能に頼らなければならない。
3つのやり方を見つけたので、メモしておく
- lockfileコマンド
- flockコマンド
- 自前のコマンド
lockfileコマンド
lockfileというコマンドがある。ただし、必ずしもどの環境にあるとは限らない。
LOCKFILE="/tmp/example.lock" lockfile "${LOCKFILE}" # 排他したい処理 rm -f "${LOCKFILE}"
上のようにLOCKFILE
というファイルをlockfileコマンドでつくって、排他したい処理を行い、最後にLOCKFILE
を削除する。
lockfileコマンドはLOCKFILE
があるときは削除されるまで待つ。オプションで待ち時間やリトライの回数等を設定できる。
ファイルの作成、削除はアトミックに行われることを利用している。
flockコマンド
これもどの環境にもあるとは限らないが、flockコマンドを使う方法。Linux限定であれば、これが一番いいと思う。このコマンドはファイルのありなしでなくて、アドバイザリロックという機能を利用します。
LOCKFILE="/tmp/example.lock" ( flock -w 10 -x 200 || { echo "ERROR: lock timeout" 1>&2 exit 1; } # 排他したい処理 ) 200> "${LOCKFILE}"
上の例は10秒間ロックを取得できなかったらエラーとなります。サブシェルがLOCKFILE
を200番のファイルディスクリプタで開いて、flockコマンドが200番のファイルディスクリプタで開かれているファイルのアドバイザリロックを取得します。サブシェルから抜けるとLOCKFILE
は閉じられるので、ロックが開放されます。例の場合は、LOCKFILE
は最初だけ作成されますが、削除はされません。既存のファイルを開いてロックを取得することも可能です。
自前のlockコマンド
環境を選びたくないなら、自前でlockコマンドを作ってしまうというのも手段だ。
LOCKFILE="/tmp/example.lock" function my_lock { local start_time cur_time start_time=$(date +%s) local timeout=10 while true; do ( umask 0777; : > "${LOCKFILE}" ) 2> /dev/null if [[ $? -eq 0 ]]; then return 0 fi cur_time=$(date +%s) if [[ $((cur_time - start_time)) -gt ${timeout} ]]; then echo "ERROR: lock timeout" 1>&2 return 1 fi done } function my_unlock { local exit_code=$? rm -f "${LOCKFILE}" exit "${exit_code}" } ( mylock || exit trap "my_unlock" EXIT HUP INT QUIT TERM # 排他したい処理 )
多分、lockfileコマンドとしていることは同じで、ファイルの作成に成功したらロックが取得でき、ファイルを削除したらロックを開放する。trap
を使うのは処理が失敗したり、途中で止めたときでも、ロックを開放することを保証するためである。