Linuxのflockコマンドとは?cronの多重起動防止とファイルロックの使い方

コマンドリファレンス

Linuxの flock コマンドは、ファイル(またはディレクトリ)に共有/排他ロックを取得し、そのロックを保持したままコマンドを実行できるユーティリティです。util-linux パッケージに含まれ、Debian/Ubuntu系では標準でインストールされています。スクリプトの多重起動防止(cronジョブの二重起動防止を含む)や、並列処理のクリティカルセクション保護に広く使われます。ロック対象が存在しない場合は自動で作成されます。(man7.org, EuroLinux)

ファイルロックとは

ファイルロックとは、複数のプロセスが同一ファイルや共有リソースに同時アクセスするときに排他制御を行う仕組みです。Linux では主に次の2種類のロックがあります。

  • 排他ロック(Exclusive Lock / Write Lock):ロック保持中は他のプロセスが同じファイルのロックを取得できない。書き込み処理など「1プロセスのみ」にしたい場合に使う(flock のデフォルト動作)。
  • 共有ロック(Shared Lock / Read Lock):複数プロセスが同時に保持できる。読み取り処理を並列化する際に使う(排他ロックとは共存不可)。-s オプションで取得。

flock のロックはアドバイザリロック(協調的ロック)です。ロックを使わないプロセスは無視してアクセスできるため、関係するすべてのスクリプト・プロセスが同じロック方式を使うことが前提となります。ロックはファイルディスクリプタ(FD)に紐付いており、プロセスが終了するとFDが閉じられ、ロックも自動的に解除されます。スクリプトが異常終了してもロックは自動解放されます。

構文(Syntax)

# 形式1: ロック対象を指定してコマンドを実行
flock [OPTIONS] FILE|DIR COMMAND [ARG...]

# 形式2: シェルの FD をロック(exec 等で FD を開いてから)
flock [OPTIONS] FD

# 便利形: 文字列でコマンドを渡す
flock [OPTIONS] FILE|DIR -c 'SHELL_COMMANDS'
  • 既定は排他ロック。共有ロックは -s。取得できない場合は待機します(-n で即時失敗)。(man7.org)

主なオプション一覧

オプション説明使用例
-s, --shared共有ロック(読取側など複数可)flock -s /tmp/data.lock -c 'cat data' (man7.org)
-x, --exclusive (-e 同義)排他ロック(書込側1つのみ、既定)flock -x /var/lock/job.lock myjob (man7.org)
-n, --nonblock取得できなければ即時失敗flock -n /tmp/backup.lock -c './backup' (man7.org)
-w, --timeout SECタイムアウト秒を指定flock -w 30 /tmp/lock -c 'do_slow' (man7.org)
-E, --conflict-exit-code N競合時の終了コードを N にflock -n -E 100 /tmp/lock cmd (ManKier)
-o, --closeコマンド実行前にロックFDを閉じる(子プロセスへロックを継承させない用途)flock -o /tmp/lock -c 'spawn-children' (ManKier)
-F, --no-forkフォークせずに直接コマンドを実行(--close非両立flock -F /tmp/lock cmd (ManKier)
-c 'CMD'コマンド文字列をシェルに渡すflock /tmp/l -c 'echo hi; sleep 1' (man7.org)
-u, --unlock明示的にアンロック(FDモードで使用)flock -u 200 (man7.org)
-v, --verbose / -h, --help / -V, --version逐次ログ / ヘルプ / バージョンflock -v -n /tmp/lock true (man7.org)

-o は「実行前に FD を閉じる」ため、子プロセスにロックを持たせたくない場合に有用です。--no-fork-F)と同時指定は不可です。(ManKier)

ロックファイルの置き場所

ロックファイルの置き場所は、実行環境と用途によって使い分けます。

場所特徴適した用途
/var/lock/OS 再起動時にクリアされる(tmpfs の場合)システム全体で共有するデーモン・バッチ処理
/run/ または /var/run/systemd 管理の RAM ディスク。再起動でクリア。/var/lock より推奨システムサービス・init スクリプト
/tmp/OS 再起動時にクリア。すべてのユーザーが書き込み可一時的な処理・簡易スクリプト
アプリ専用ディレクトリ権限管理が明確。絶対パスで指定することアプリ固有のバッチ処理

絶対パスで指定することが重要です。flock ./myapp.lock のように相対パスで指定すると、カレントディレクトリが変わった瞬間に別のファイルをロックしてしまい、意図した排他制御ができなくなります。cron から実行するスクリプトでは特に注意してください。

# 悪い例:相対パス(cron 実行時はホームディレクトリになることがある)
flock ./myapp.lock -c './batch.sh'

# 良い例:常に絶対パスで指定
flock /var/lock/myapp.lock -c '/usr/local/bin/batch.sh'

実行例

多重起動を防ぐ(即時失敗)

説明: すでに動作中なら即終了(終了コード 1 など)。
コマンド:

flock -n /var/lock/myjob.lock -c '/usr/local/bin/myjob'
echo "exit=$?"

出力例(ロック獲得失敗時):

exit=1

-E で任意コードに変更可) (man7.org, ManKier)

cron で“かぶり”を防止

説明: 1分おきでも重ならないように。
コマンド(crontab 行):

*/1 * * * * flock -n /tmp/backup.lock -c '/usr/local/sbin/backup'

(ex1-lab)

FD モードでクリティカルセクションを囲む(シェル)

説明: スクリプト全体でロックを保持。
コマンド:

exec 200>/var/lock/task.lock          # FD=200 を開く(存在しなければ作成)
flock -n 200 || { echo "busy"; exit 1; }  # 取れなければ即終了

# ここからクリティカルセクション
update_db
generate_report

flock -u 200   # 明示的にアンロック(終了時にFDが閉じられても解除されます)

(man7.org, Stack Overflow)

共有ロックで“読み手”を並列許可

説明: 読み取り処理は並行、書き込みは別途排他ロックで制御。
コマンド:

flock -s /tmp/data.lock -c 'cat bigdata.csv | wc -l'

(man7.org)

ディレクトリをロック対象にする

説明: ファイルだけでなくディレクトリにもロックできます。
コマンド:

flock -w 10 /var/lock/myapp 'sh -c "touch /tmp/doing; sleep 2"'

(EuroLinux)

エラー例:--no-fork と --close の併用

説明: 併用するとロックを保持するプロセスがなくなるためエラー。
コマンド:

flock -F -o /tmp/lock -c 'echo NG'

出力例(例): オプション不正のエラー
根拠: 両者は非両立です。(ManKier)

実務ユースケース

cronジョブの二重起動を完全に防ぐスクリプト

定期実行バッチで前回の処理が終わっていないのに次の起動が始まる「かぶり問題」を、flock を使ったスクリプトで解決できます。

#!/bin/bash
# /usr/local/sbin/daily-report.sh
LOCK_FILE="/var/lock/daily-report.lock"
LOG_FILE="/var/log/daily-report.log"

(
  flock -n 200 || {
    echo "[$(date '+%F %T')] 既に実行中のため終了" >> "$LOG_FILE"
    exit 1
  }

  echo "[$(date '+%F %T')] ジョブ開始" >> "$LOG_FILE"

  # メイン処理
  /usr/local/bin/generate-report

  echo "[$(date '+%F %T')] ジョブ完了" >> "$LOG_FILE"
) 200>"$LOCK_FILE"

crontab への登録例(毎日2時に実行):

0 2 * * * /usr/local/sbin/daily-report.sh

ポイント:() でサブシェルを作り、FD 200 番でロックを取得します。サブシェルが終了するとFDが自動で閉じられ、ロックも解除されます。

バッチ処理の共有リソースをロックで保護する

複数のワーカープロセスが並列で動く場合、共有リソース(DBへの書き込みや共有ファイルの更新)だけを flock で直列化し、スループットを落とさずに安全性を確保できます。

#!/bin/bash
# 共有リソースのみロック対象にするバッチワーカー
LOCK_FILE="/var/lock/batch-worker.lock"
RESULT_FILE="/tmp/batch-results.log"

process_item() {
  local item="$1"

  # 独立した処理は並列で実行(ロック不要)
  local result
  result=$(heavy_computation "$item")

  # 共有リソース(結果ファイルへの追記)だけロック
  flock -w 30 "$LOCK_FILE" bash -c "echo '$result' >> '$RESULT_FILE'"

  if [ $? -ne 0 ]; then
    echo "ERROR: ロック取得タイムアウト (item=$item)" >&2
    return 1
  fi
}

export -f process_item

# 4並列でCSVを処理
find /data/input -name "*.csv" | xargs -P 4 -I{} bash -c 'process_item "$@"' _ {}

よくある失敗例

失敗1:ロックファイルを消せばロックが解除されると誤解する

flock のロックはファイルの存在ではなくFD(ファイルディスクリプタ)に紐付いています。ロックファイルを削除しても、そのファイルを開いているプロセスが存在する間はロックは有効です。逆に、プロセスが終了していればファイルが残っていてもロックは既に解除済みです。

# NG: ロックファイルを削除してもロックは解除されない
rm /var/lock/myjob.lock   # これではロックは解除されない

# 正しい対処: 保持プロセスを確認して終了させる
fuser /var/lock/myjob.lock   # ロックを持つプロセスの PID を確認
kill <PID>                    # プロセスを終了させる

失敗2:スクリプト全体ではなく一部だけロックしてしまう

多重起動防止が目的なのに、スクリプトの一部処理だけを flock で囲むと、スクリプト全体は並行して実行されてしまいます。多重起動防止にはスクリプト先頭でロックを取得し、処理全体をロック保護してください。

#!/bin/bash
# 悪い例:一部の処理だけロック(スクリプト全体は多重起動する)
heavy_pre_process   # ここは並行実行される

flock /var/lock/myapp.lock -c 'write_db'  # DB への書き込みだけロック

heavy_post_process  # ここも並行実行される
#!/bin/bash
# 良い例:スクリプト全体をロック(先頭でロックを取得)
(
  flock -n 200 || { echo "既に実行中"; exit 1; }

  heavy_pre_process
  write_db
  heavy_post_process
) 200>/var/lock/myapp.lock

失敗3:相対パスでロックファイルを指定する

相対パスで指定するとカレントディレクトリに依存し、実行環境によって異なるファイルをロックすることになります。cron から実行するスクリプトでは、カレントディレクトリがホームディレクトリなどになる場合があり、意図しないパスをロックしてしまいます。

# 悪い例:相対パス(cron 実行時にカレントディレクトリが変わると別ファイルをロック)
flock ./myapp.lock -c './batch.sh'

# 良い例:常に絶対パスで指定
flock /var/lock/myapp.lock -c '/usr/local/bin/batch.sh'

失敗4:権限不足でロックファイルを作れない

ロックファイルが存在しない場合、flock は自動でファイルを作成しようとします。そのディレクトリへの書き込み権限がない場合はエラーになります。

# エラー例:/var/lock/ に書き込み権限がない場合
$ flock /var/lock/myjob.lock -c 'true'
flock: /var/lock/myjob.lock: Permission denied

# 確認方法
ls -la /var/lock/

# 対処1: 実行ユーザーに書き込み権限を付与
sudo chown $(whoami) /var/lock/   # または適切なグループ設定

# 対処2: 権限のあるディレクトリに変更
flock /tmp/myjob.lock -c './job.sh'

トラブルシューティング

ロックが解除されない・ロックファイルが残った場合

まず確認することflock によるロックはプロセスが終了すると自動的に解除されます。ロックファイル自体が残っていても、保持プロセスがなければロックは取得できます。

# ロックを保持しているプロセスを確認する
fuser /var/lock/myjob.lock

# または lsof で詳細を確認
lsof /var/lock/myjob.lock

# プロセスが存在しない場合 → ロックは既に解除済み(取得可能)
# プロセスが存在する場合 → そのプロセスの終了を待つか強制終了する
kill -9 <PID>

ロックファイルの削除についてflock のロックはファイルの存在ではなくFDに紐付くため、ファイルを削除してもプロセスが生きている間はロックは有効です。逆に、プロセスが死んでいればファイルが残っていてもロックは解除済みです。ファイルは削除しても問題ありませんが、削除は根本解決にならない点に注意してください。

タイムアウト(-w)を使った安全な待機と失敗制御

-n(即時失敗)ではなく -w SEC(N秒待機)を使うと、一時的な競合でも処理が継続できます。タイムアウト後の処理は終了コードで分岐してください。

# 30秒待機してからあきらめる
flock -w 30 /var/lock/slow-job.lock -c './slow-process.sh'
EXIT_CODE=$?

case $EXIT_CODE in
  0)
    echo "正常完了"
    ;;
  1)
    echo "ロック取得失敗またはコマンドエラー" >&2
    exit 1
    ;;
esac

タイムアウト時間の目安:通常処理時間の2〜3倍を設定すると、一時的な遅延は吸収しつつ異常な長時間ロックは検知できます。-E オプションでタイムアウト専用の終了コードを設定すれば、コマンドエラーとの区別も可能です。

# -E 100 でタイムアウト時の終了コードを 100 に設定
flock -w 30 -E 100 /var/lock/job.lock -c './job.sh'
case $? in
  0)   echo "成功" ;;
  100) echo "タイムアウト(ロック競合)" >&2 ;;
  *)   echo "コマンドエラー" >&2 ;;
esac

flock と他のロック方法との違い

方法特徴適した場面
flock(Linux コマンド)プロセス終了で自動解放。既存コマンドをラップできるシェルスクリプト・cronジョブの多重起動防止
lockfile(procmail)ファイルの存在でロック。自動解放なし(明示削除が必要)古いシステム・シンプルな用途
setlock(daemontools)コマンドが終わるまでロック保持。daemontools 環境向けdaemontools 管理のサービス
mkdir の原子作成手軽だが解除漏れ検知・タイムアウトは自前実装が必要シンプルな用途・ポータビリティ重視
flock(2)(システムコール)C プログラムから呼ぶカーネルAPI。NFS での挙動差ありC/C++ アプリのロック制御

PHPの flock() との違い

PHP にも flock() 関数が存在しますが、Linux コマンドの flock とは異なります。

  • PHPの flock():PHPスクリプト内からファイルロックを取得する関数fopen() で開いたファイルハンドルを引数に取る。ロック種別は LOCK_EX(排他)/ LOCK_SH(共有)/ LOCK_UN(解除)/ LOCK_NB(ノンブロッキング)。
  • Linuxコマンドの flock:シェルから任意のコマンドをロック付きで実行するツール。PHPスクリプトから呼ぶこともできる(shell_exec('flock ...'))が、通常はPHPの組み込み関数を使う。

「flock」で検索すると PHP マニュアルの flock() ページも上位に表示されますが、本記事で扱っているのはLinuxコマンドラインの flock(util-linux パッケージ)です。

関連コマンド

  • flock(2) : カーネルのアドバイザリロックAPI。NFS などでの挙動差の説明もこちらにあり。(man7.org)
  • fcntl(2) : POSIX レコードロック(NFS での相互運用に利用)。(man7.org)
  • lockfile(procmail)/ setlock(daemontools): ロックファイルを用いる別系統の直列化ツール。
  • mkdir の原子作成を使う簡易ロック:手軽だが解除漏れ検知などは自前実装が必要。

備考

  • アドバイザリ: flock のロックは協調的(アドバイザリ)です。ロックを無視して I/O することも可能なので、協調する全プロセスで flock など同一方式を用いてください。(man7.org)
  • NFS / ネットワークFS: Linux 2.6.12 以降の NFS クライアントでは、flock()fcntl のバイト範囲ロックでエミュレートされ、相互に干渉します(排他は書き込み用にオープンが必要)。古い環境・他実装では非対応/局所動作のことがあるため注意。(Arch Linux マニュアルページ)
  • ディスクリプタ継承: fork で複製された子は同じロックを保持できます。子にロックを持たせたくない場合は --close を検討。(ManKier, ウィキペディア)
  • ロック対象の種類: ファイル/ディレクトリどちらでも可。対象が無ければ作成されます。(EuroLinux)
  • 終了コード: 競合で失敗した場合は既定で 1 を返します(-E で変更可)。-n なしでは待機します。(man7.org, ManKier)

関連記事

参考

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント