シェルスクリプトで学ぶ排他制御

 シェルスクリプトで学ぶ排他制御

以下動画のテキストです。
https://youtu.be/RPKt1p2SkxY

842515eaf8fbb2dfcc75197e7797dc15?s=128

Satoru Takeuchi

September 19, 2020
Tweet

Transcript

  1. シェルスクリプトで学ぶ排他制御 Sep 19th, 2020 Satoru Takeuchi Twitter: satoru_takeuchi, EnSatoru 1

  2. 排他制御 • 以下のことを一つの処理からしか操作できないようにする ◦ コード上のある箇所 (クリティカルセクション )の実行 ◦ あるリソースを操作する •

    抽象的で意味わからん! ◦ これ聞くだけで「なるほど!」ってなる人はあんまりいない • 実例を使って説明 ◦ 多くの人から見て身近なシェルスクリプトを使う 2
  3. 身近な排他制御の使用例 • apt系コマンドが以下のようなメッセージと共に異常終了 • この問題の発生ロジック ◦ apt系コマンドは同時に複数実行できないようになっている ◦ /var/lib/apt/lists/lockが存在しなければ実行、存在すれば異常終了する ▪

    上記のようなファイルをロックファイルと呼ぶ ◦ 実際に発生するのはほとんど次のような場合 1. apt系コマンド実行中にC-cで異常終了させたりするとファイルが残る 2. 次に実行すると必ず失敗 3. ユーザがファイルを消せば再実行可能になる 3 E: Could not get lock /var/lib/apt/lists/lock – open (11: Resource temporarily unavailable)
  4. 身近な排他制御の使用例 • apt系コマンドが以下のようなメッセージと共に異常終了 • この問題の発生ロジック ◦ apt系コマンドは同時に複数実行できないようになっている ◦ /var/lib/apt/lists/lockが存在しなければ実行、存在すれば異常終了する ▪

    上記のようなファイルをロックファイルと呼ぶ ◦ 実際に発生するのはほとんど次のような場合 1. apt系コマンド実行中にC-cで異常終了させたりするとファイルが残る 2. 次に実行すると必ず失敗 3. ユーザがファイルを消せば再実行可能になる 4 E: Could not get lock /var/lib/apt/lists/lock – open (11: Resource temporarily unavailable) 今回のテーマ
  5. 最初に思いつく実装: シェルスクリプトで表現 5 if [ -e /var/lib/apt/lists/lock ] ; then

    echo <さっきのメッセージ> >&2 exit 1 fi touch /var/lib/apt/lists/lock aptの実処理 rm -f /var/lib/apt/lists/lock ここがクリティカルセクション ①最初にファイルの存在確認 ②なければファイルを作って 他の処理が割り込めないように してから先に進む ③最後にファイルを消して、他のプロセスが実処理を実行できるようにする
  6. これはうまく動かない • 例: apt Aとapt Bを同時に実行 6 if [ -e

    /var/lib/apt/lists/lock ] ; then echo <さっきのメッセージ> >&2 exit 1 fi touch /var/lib/apt/lists/lock aptの実処理 rm -f /var/lib/apt/lists/lock if [ -e /var/lib/apt/lists/lock ] ; then echo <さっきのメッセージ> >&2 exit 1 fi touch /var/lib/apt/lists/lock aptの実処理 rm -f /var/lib/apt/lists/lock apt A apt B
  7. これはうまく動かない • 2つのif文が同時に実行。両方ファイルが存在しないと判定する 7 if [ -e /var/lib/apt/lists/lock ] ;

    then echo <さっきのメッセージ> >&2 exit 1 fi touch /var/lib/apt/lists/lock aptの実処理 rm -f /var/lib/apt/lists/lock if [ -e /var/lib/apt/lists/lock ] ; then echo <さっきのメッセージ> >&2 exit 1 fi touch /var/lib/apt/lists/lock aptの実処理 rm -f /var/lib/apt/lists/lock apt A apt B ファイルは無いな… ファイルは無いな…
  8. これはうまく動かない • 2つとも先に進んでaptの実処理を実行 ◦ aptの処理が正しく動作しない &データベースがブッ壊れる 8 if [ -e

    /var/lib/apt/lists/lock ] ; then echo <さっきのメッセージ> >&2 exit 1 fi touch /var/lib/apt/lists/lock aptの実処理 rm -f /var/lib/apt/lists/lock if [ -e /var/lib/apt/lists/lock ] ; then echo <さっきのメッセージ> >&2 exit 1 fi touch /var/lib/apt/lists/lock aptの実処理 rm -f /var/lib/apt/lists/lock apt A apt B 先に進もう 先に進もう
  9. もうちょっとわかりやすい例 • いわゆるカウンタプログラムを使う ◦ countファイルの初期値は 0 ◦ 一回実行するごとにファイルの中身の数値を 1つ足す ◦

    すでに同じプログラムが実行中ならすぐリトライ (スピンロック) • 最初に思いつく実装: inc-counter.sh 9 while : ; do if [ ! -e lock ] ; then break fi done touch lock TMP=$(cat count) echo $((TMP + 1)) >count rm -f lock ここがクリティカルセクション
  10. inc-counterプログラムの並列実行 • カウントを1000増加させるスクリプト(inc-counter-1000.sh)を同時に2つ実行 • 結果 ◦ 期待: 最終的にcountの値が2000になる ◦ 実際:

    2000より小さな値になる(ことがある) ▪ 同じCPU上で実行すると起きやすい 10 for ((i=0;i<1000;i++)) ; do ./inc-counter.sh done for ((i=0;i<1000;i++)) ; do ./inc-counter.sh done
  11. うまくいかない理由 • 2つのプログラムが両方クリティカルセクションに入ることがある 11 while : ; do if [

    ! -e lock ] ; then break fi done touch lock TMP=$(cat count) echo $((TMP + 1)) >count rm -f lock 2つのプログラムが同時にここを実行するとアウト 2つともクリティカルセクションに入り、 countの計算が狂う 例: カウンタA,Bを同時に動かす&countの初期値は0 1. countから0を読み出す 2. countに1を書き込む カウンタA 1. countから0を読み出す 2. countに1を書き込む カウンタB 終了時のcountは2であるべきだが1になる
  12. 解決方法 • 以下2つの処理を他の処理に割り込まれないよう実行(アトミック操作) 1. ロックファイルが存在するかどうかをチェック 2. 存在していれば成功、そうでなければ失敗 • シェルスクリプトからはflockコマンドが使える ◦

    カウンタプログラムは以下のように実装できる inner-inc-counter.sh ▪ 12 TMP=$(cat count) echo $((TMP + 1)) >count while ((i=0;i<1000;i++)) ; do flock lock ./inner-counter.sh done “lock”ファイルを使って上記アトミック操作を実現 inner-inc-counter.sh correct-inc-counter-1000.sh
  13. flockの内部 • flock()システムコールを使う ◦ あるファイルに対して flock(O_EX)を呼び出すと以下2つの処理をアトミックに実行 1. ファイルに対してロックがかかっているかどうかを確認 2. アンロック状態ならロックしてから正常終了、ロック済なら異常終了

    ◦ flock(O_UN)でアンロック ◦ 詳細は”man 2 flock”を参照 • Linuxならopen(O_CREAT|O_EXCL)を使うという手もある ◦ “その14 マウント中のファイルシステムの破壊を防ぐ小技 ”で紹介しました ▪ https://youtu.be/Hbgtt1dj9tM 13
  14. まとめ • 排他制御: 以下のことを一つの処理からしか操作できないようにする ◦ コード上のある箇所 (クリティカルセクション )の実行 ◦ あるリソースを操作する

    • 身近なところではapt系コマンドの同時実行を防ぐために使われている • シェルスクリプトならflockコマンドで排他制御ができる 14