データを入れたファイルを読んだり書いたりしていくうちに、あるタイミングで大事なデータが壊れてしまうことがあります。 多くの場合その原因は、「不完全な状態のデータの読み込み」や「他プロセスとの競合した書き込み」だったりします。 こういうときに使えるのが、ファイルロックということになります。 でもこのファイルロック、正しく書かないと意味の無いものになりかねません。
データを入れたファイルを読んだり書いたりしていくうちに、あるタイミングで大事なデータが壊れてしまうことがあります。 多くの場合その原因は、「不完全な状態のデータの読み込み」や「他プロセスとの競合した書き込み」だったりします。
こういうときに使えるのが、ファイルロックということになります。
でもこのファイルロック、正しく書かないと意味の無いものになりかねません。
ファイルロック専用の関数がflock()関数になります。 ▼ 基本的な流れは ロックしたいファイルをfopen()関数でオープンしてファイルポインタを取得 flock()関数に、取得したファイルポインタおよびロックモードを指定 あとはファイルロックができたかできなかったかで処理が分かれます。 ロックが確保できれば、処理を続行して最後にfclose()関数でロック開放 ロックが確保できなければ、できるまで繰り返すかあきらめて処理を終了させる。 ▼ ロックモード指定用の4つの定数 定数説明 LOCK_SH共有ロック(同時に複数プロセスから読めて、書けない) LOCK_EX排他ロック(同時に1プロセスだけが読み書きできる) LOCK_UNロック開放 LOCK_NBロックを確保できない時点ですぐにやめる非ブロック。(Windowsでは未対応) そのままではブロックモードといって、ロックを確保できるまでいつまでも待ち続ける状態になるが、LOCK_NB指定にすると非ブロックモードになりロックできないとすぐ見切りをつけて偽を返す。 LOCK_UNは普通は使われない。ロック開放はfclose()関数でやるのが鉄則。 ▼ どのファイルをロックするのか まずロックを掛けるファイルとしてfopen()関数でオープンするのは、「書き替えファイル自身」か「ロック用のファイル」を別に作るか2通りある。 ここでは「ロック用のファイル」を作ってこれにロックを掛けるやり方で進めていく。 ということで適当なロック用のファイル名を決めて、追加書き込みモード"a"でオープンする。 なぜ追加書き込みモードかというと、新規オープン"w"だとファイルが存在してる場合にファイルを作成し直すことで、もしこれがロック中だと書き込み権が競合してデータが壊れる可能性があるから。 追加書き込みモードなので、もしロックファイルがまだ存在していなくてもオープン時に作成されるし、既に存在していても作り直すことはせずにオープンしてくれる。 つまりロック用ファイルの有無を気にせずに済むから便利。
ファイルロック専用の関数がflock()関数になります。
そのままではブロックモードといって、ロックを確保できるまでいつまでも待ち続ける状態になるが、LOCK_NB指定にすると非ブロックモードになりロックできないとすぐ見切りをつけて偽を返す。
LOCK_UNは普通は使われない。ロック開放はfclose()関数でやるのが鉄則。
まずロックを掛けるファイルとしてfopen()関数でオープンするのは、「書き替えファイル自身」か「ロック用のファイル」を別に作るか2通りある。
ここでは「ロック用のファイル」を作ってこれにロックを掛けるやり方で進めていく。
ということで適当なロック用のファイル名を決めて、追加書き込みモード"a"でオープンする。
なぜ追加書き込みモードかというと、新規オープン"w"だとファイルが存在してる場合にファイルを作成し直すことで、もしこれがロック中だと書き込み権が競合してデータが壊れる可能性があるから。
追加書き込みモードなので、もしロックファイルがまだ存在していなくてもオープン時に作成されるし、既に存在していても作り直すことはせずにオープンしてくれる。 つまりロック用ファイルの有無を気にせずに済むから便利。
ロックを確保できるまでいつまでも待ち続ける「ブロックモード」を書いてみます。 一通りできましたので次にこれを関数にしてみます。 ・ファイルロック(ブロックモード)の自作関数 あまり関数化するほどのコード量ではないですが、これを作っておけばいろいろなカテゴリレベルでロック用ファイル名を変えれば細かなロックができそうです。
ロックを確保できるまでいつまでも待ち続ける「ブロックモード」を書いてみます。
一通りできましたので次にこれを関数にしてみます。
あまり関数化するほどのコード量ではないですが、これを作っておけばいろいろなカテゴリレベルでロック用ファイル名を変えれば細かなロックができそうです。
ロックを確保できなければ処理をあきらめる「非ブロックモード」のファイルロックを書いてみます。 こちらも関数化してみます。 ただし、一度の失敗であきらめてしまうのは物足りないので、50回ほどリトライ掛けてみるようにします。 ・ファイルロック(非ブロックモード)の自作関数 0.1秒遅延を最大50回トライするので非常にサーバが混み合ってる時にはヘタしたら5秒待った挙げ句にロック確保できずに処理を終わらせるということがあるかも知れません。 サーバへのリクエストが鬼のように増えてくると、場合によってはこうやってロック確保できないプロセスを終わらせていったほうがサーバには優しいかも?
ロックを確保できなければ処理をあきらめる「非ブロックモード」のファイルロックを書いてみます。
こちらも関数化してみます。 ただし、一度の失敗であきらめてしまうのは物足りないので、50回ほどリトライ掛けてみるようにします。
0.1秒遅延を最大50回トライするので非常にサーバが混み合ってる時にはヘタしたら5秒待った挙げ句にロック確保できずに処理を終わらせるということがあるかも知れません。
サーバへのリクエストが鬼のように増えてくると、場合によってはこうやってロック確保できないプロセスを終わらせていったほうがサーバには優しいかも?
ロックを確保できればあとは目的のデータファイルを自由に書き替えることができるということで、今まさに書き込み処理中。。。 といった時に、突然サーバがダウンして再起動するという事態になってしまいました・・・。 どうしましょうか・・ データファイルを書き込んでる途中だったので、次回の処理で書きかけの半端なデータを読み込んで処理するとうまく動かないことが想定されます。 こういう事態に陥らないように、データファイルへの直接書き込みは避けて、一旦は一時ファイルを作成してそこに書き込み、無事に最後まで書き込めたらリネームしてデータファイルにすげ替えるという方法を考えます。
ロックを確保できればあとは目的のデータファイルを自由に書き替えることができるということで、今まさに書き込み処理中。。。
といった時に、突然サーバがダウンして再起動するという事態になってしまいました・・・。
どうしましょうか・・
データファイルを書き込んでる途中だったので、次回の処理で書きかけの半端なデータを読み込んで処理するとうまく動かないことが想定されます。
こういう事態に陥らないように、データファイルへの直接書き込みは避けて、一旦は一時ファイルを作成してそこに書き込み、無事に最後まで書き込めたらリネームしてデータファイルにすげ替えるという方法を考えます。
ロックは絶妙なタイミングで掛けて、絶妙なタイミングで開放しないといけません。 ロックはデータ書き替えのアクセス権なので、できるだけロックを短時間で済ませることによってより多くのユーザに対応できることになります。 例えば掲示板の書き込み処理において、投稿を反映させる処理を考えます。 まずいきなりロックを掛けてしまってはロスが出てしまいます。 では以下のような処理段階がある場合、どの段階でロックを掛ければいいのか考えます。 投稿フラグの有無をチェック システムが運用中で書き込みOKな状態かチェック 投稿者の回線がアクセス禁止設定になっていないかチェック 投稿内容(文字数・改行数・文字コード・必須入力など)が問題ないかチェック 一定時間経ってからの投稿かどうかチェック 二重投稿かどうかチェック 投稿を反映させる 以上のような処理段階があるとすると、[4]までだったら投稿時にサーバ側の保持する情報が書き替えられることはないので[4]の直後にロックを掛けることにします。 そして[7]完了後にロックを開放します。 [5]や[6]では投稿の度に書き替えられるキャッシュデータがあるので、この後にロックを掛けてしまうと前回投稿が反映されていないキャッシュデータをチェックしてしまうことになりかねません。
ロックは絶妙なタイミングで掛けて、絶妙なタイミングで開放しないといけません。 ロックはデータ書き替えのアクセス権なので、できるだけロックを短時間で済ませることによってより多くのユーザに対応できることになります。
例えば掲示板の書き込み処理において、投稿を反映させる処理を考えます。
以上のような処理段階があるとすると、[4]までだったら投稿時にサーバ側の保持する情報が書き替えられることはないので[4]の直後にロックを掛けることにします。 そして[7]完了後にロックを開放します。
[5]や[6]では投稿の度に書き替えられるキャッシュデータがあるので、この後にロックを掛けてしまうと前回投稿が反映されていないキャッシュデータをチェックしてしまうことになりかねません。