【PHP】try-lockを簡易実装する一つの方法
サイト移転のお知らせ2025年4月22日(火)ファンブログ終了です。それに伴いサイト移転します。
移転先: https://www.wcguru.net/
このページの移転先:https://www.wcguru.net/archives/229
一つしかない資源(リソース)へ同時に更新、書き込みが発生する事態は極力避けたいです。FastCGI(nginxやapache)で動作させるPHPでは、同時間に複数のアクセスがあったりするので、一つのリソースに対する排他制御が必要になることがあります。
C#のような言語では、lockステートメントやMonitor.TryEnterなどを利用することで実現できます。PHPではこのようなステートメントや高級な仕組みが用意されていません。
PHP でもコンパイル指定次第でセマフォ(sem_get 、sem_acquire)やMutexが使えます。レンタルサーバー上にあるPHPでは利用できないことが多いですね![](https://fanblogs.jp/_images_g/a26.png)
セマフォはサーバー上資源を利用する仕組みだと思っています。そのためデットロック等の異常事態が発生した際、復旧にはサーバー管理者権限が必要になったりするので仕方ないと思っています。
セマフォやMutexを利用しない排他制御の手段は、ファイルロック(flock)です。
【排他制御の注意点】
色々考えるととても面倒ですね。手を抜いて、カンタンに実装できる方法がこちらです。
PHPのtry-lockはSQLiteを使うのがカンタンでした
SQLiteはどのレンタルサーバーでも利用できる状態になっていると思っています。環境を気にせずに利用できます。
SQLiteは同時書き込みに対応していません。同時に書き込みトランザクションは1つだけという制限を持っています。
これを利用することで自前でflock等の実装をする必要がなくなります。
実装例は↓のような感じです。
使い方は、上記のソースをinclude、requireした後、こんな感じで処理します。
FastCGIで動作させる場合、PHPの処理が終わったタイミングで自動開放されるので資源の開放処理は不要です。
SQLiteを使うことで、シンプルに実装することができました。
単純明快なコードですが、同タイミングで複数スレッド、プロセスによるアクセスでtryAquireは間違いを犯すことがあります。
資源が獲得できた場合に別のSQLiteデータベースに対する更新処理を行っています。
この更新処理でdatabase is lockが発生します。発生事態は稀ですが、そういうことが起こる前提でご利用ください。
失敗した場合、ユニークなファイルで所定ディレクトリに格納し、獲得できたタイミングでまとめて処理するようにしていますよ。
C#のような言語では、lockステートメントやMonitor.TryEnterなどを利用することで実現できます。PHPではこのようなステートメントや高級な仕組みが用意されていません。
PHP でもコンパイル指定次第でセマフォ(sem_get 、sem_acquire)やMutexが使えます。レンタルサーバー上にあるPHPでは利用できないことが多いですね
![](https://fanblogs.jp/_images_g/a26.png)
セマフォはサーバー上資源を利用する仕組みだと思っています。そのためデットロック等の異常事態が発生した際、復旧にはサーバー管理者権限が必要になったりするので仕方ないと思っています。
セマフォやMutexを利用しない排他制御の手段は、ファイルロック(flock)です。
【排他制御の注意点】
- ロックの機構は完璧な制御が出来ないと思っています。
- 全く同時のタイミングで複数のリクエストが来た場合、どのようなロック実装手段でも想定外の事態が起こりやすいです。
- そのため、排他制御で資源を獲得できた場合でも資源更新エラーに備える必要があると思っています
- ロックの機構は極力シンプルに、素早く完了させる必要があります
- ロックしたまま、異常終了してしまうと誰も獲得できない事態が発生します。注意深く設計する必要があります。
色々考えるととても面倒ですね。手を抜いて、カンタンに実装できる方法がこちらです。
PHPのtry-lockはSQLiteを使うのがカンタンでした![](https://fanblogs.jp/_images_g/a2.png)
SQLiteはどのレンタルサーバーでも利用できる状態になっていると思っています。環境を気にせずに利用できます。
SQLiteは同時書き込みに対応していません。同時に書き込みトランザクションは1つだけという制限を持っています。
これを利用することで自前でflock等の実装をする必要がなくなります。
実装例は↓のような感じです。
<?php
class MyLock {
private ?SQLite3 $lockObject = null;
function __construct($lockfile){
$this->lockObject = new SQLite3( $lockfile );
$this->lockObject->enableExceptions(true);
}
public function tryAquire() : bool {
try{
$this->lockObject->exec("BEGIN IMMEDIATE;");
return true;
}catch(Exception $e){
return false;
}
}
public function release(){
if( is_object($this->lockObject) == false ){
return ;
}
try{
$this->lockObject->exec("end");
}catch(Exception $e){
return ;
}
}
}
- とてもカンタンな実装にしているので不足しているポイントがあればご自身で成長させてください。
- tryAquire()は、ロックを試みてロックできない場合は、false。獲得できた場合trueを返します。
- release()は所有者の確認をしないロック解除メソッドです。
使い方は、上記のソースをinclude、requireした後、こんな感じで処理します。
$lock = new MyLock("file.lock");
if( $lock->tryAquire() ){
// 資源が獲得できた場合の処理
}else{
// 獲得できなかった場合の処理
}
FastCGIで動作させる場合、PHPの処理が終わったタイミングで自動開放されるので資源の開放処理は不要です。
SQLiteを使うことで、シンプルに実装することができました。
まとめ:ロックの競合は発生します。
単純明快なコードですが、同タイミングで複数スレッド、プロセスによるアクセスでtryAquireは間違いを犯すことがあります。
資源が獲得できた場合に別のSQLiteデータベースに対する更新処理を行っています。
この更新処理でdatabase is lockが発生します。発生事態は稀ですが、そういうことが起こる前提でご利用ください。
失敗した場合、ユニークなファイルで所定ディレクトリに格納し、獲得できたタイミングでまとめて処理するようにしていますよ。
- 初期費用0円(2022年8月4日に撤廃)
- 安定したサイト運営が可能(CPU/メモリ リソース保証 6コア/8GB~)
- 契約中は独自ドメインつき 独自ドメイン永久無料特典があります
- 最大10日間無料で、サーバーのお試し利用ができます
- ディスクキャッシュも高速 NVMe SSDを採用(300GB~)
- 大量アクセスでも安心 転送量は無制限
【このカテゴリーの最新記事】
-
no image
-
no image
-
no image
-
no image
-
no image
この記事へのコメント
コメントを書く
この記事へのトラックバックURL
https://fanblogs.jp/tb/12341573
※ブログオーナーが承認したトラックバックのみ表示されます。
この記事へのトラックバック