ホームページ  >  記事  >  バックエンド開発  >  PHP のファイル ロック、ミューテックス ロック、読み取り/書き込みロックの詳細な説明

PHP のファイル ロック、ミューテックス ロック、読み取り/書き込みロックの詳細な説明

*文
*文オリジナル
2017-12-29 18:02:483724ブラウズ

この記事では主に、PHP プログラムにおけるファイル ロック、ミューテックス ロック、および読み取り/書き込みロックの使用テクニックを紹介します。特に、sync モジュールと pthreads モジュールでの使用例に焦点を当てています。お役に立てれば幸いです。

ファイルロック
正式名称はアドバイザリーファイルロックで、本の中で言及されています。 このタイプのロックは比較的一般的です。たとえば、mysql と php-fpm が開始された後、プロセス ID を記録する pid ファイルが作成されます。このファイルはファイル ロックです。

このロックにより、プロセスが繰り返し実行されるのを防ぐことができます。たとえば、crontab を使用する場合、1 つのタスクは 1 分ごとに実行されるように制限されますが、プロセス ロックを解決するために使用されない場合、このプロセスは 1 分以上実行される可能性があります。競合する場合、2 つのプロセスを一緒に実行すると問題が発生します。

PID ファイル ロックを使用するもう 1 つの利点は、プロセスが停止信号または再起動信号をそれ自体に送信するのに便利であることです。たとえば、php-fpm を再起動するコマンドは

kill -USR2 `cat /usr/local/php/var/run/php-fpm.pid`です
pid ファイルに記録されているプロセスに USR2 シグナルを送信します。信号はプロセス通信に属し、別の章を開きます。

PHP のインターフェースは flock であり、ドキュメントは比較的詳細です。まず定義を見てみましょう。 bool flock ( resource $handle , int $operation [, int &$wouldblock ] )。

  • $handle はファイル システム ポインターで、通常は fopen() によって作成されるリソースです。 。これは、flock を使用するにはファイルを開く必要があることを意味します。

  • $operation は操作の種類です。

  • &$wouldblock ロックがブロックしている場合、この変数は 1 に設定されます。

この関数はデフォルトでブロックしていることに注意してください。非ブロックにしたい場合は、次の関数を追加できます。ビットマスク LOCK_NB を操作に追加してテストしてください。


$pid_file = "/tmp/process.pid";
$pid = posix_getpid();
$fp = fopen($pid_file, 'w+');
if(flock($fp, LOCK_EX | LOCK_NB)){
  echo "got the lock \n";
  ftruncate($fp, 0);   // truncate file
  fwrite($fp, $pid);
  fflush($fp);      // flush output before releasing the lock
  sleep(300); // long running process
  flock($fp, LOCK_UN);  // 释放锁定
} else {
  echo "Cannot get pid lock. The process is already up \n";
}
fclose($fp);

それを process.php として保存し、php process.php & を実行してから、php process.php を再度実行すると、エラー メッセージが表示されます。 flock には、共有ロック LOCK_SH もあります。

ミューテックス ロックと読み書きロック
同期モジュールのミューテックス:
ミューテックスは、相互排他を意味する複合語です。 pecl を使用して同期モジュール、pecl install sync をインストールします。 ドキュメント内の SyncMutex には、lock と lock の 2 つのメソッドしかありません。コードのテストに直接進みましょう。 IDE で書いたわけではないので、cs は非常に見苦しいですが、無視してください。


$mutex = new SyncMutex("UniqueName");

for($i=0; $i<2; $i++){
  $pid = pcntl_fork();
  if($pid <0){
    die("fork failed");
  }elseif ($pid>0){
    echo "parent process \n";
  }else{
    echo "child process {$i} is born. \n";
    obtainLock($mutex, $i);
  }
}

while (pcntl_waitpid(0, $status) != -1) { 
  $status = pcntl_wexitstatus($status); 
  echo "Child $status completed\n"; 
}

function obtainLock ($mutex, $i){
  echo "process {$i} is getting the mutex \n";
  $res = $mutex->lock(200);
  sleep(1);
  if (!$res){
    echo "process {$i} unable to lock mutex. \n";
  }else{
    echo "process {$i} successfully got the mutex \n";
    $mutex->unlock();
  }
  exit();
}

それをmutex.phpとして保存し、php mutex.phpを実行すると、出力は次のようになります


parent process 
parent process 
child process 1 is born. 
process 1 is getting the mutex 
child process 0 is born. 
process 0 is getting the mutex 
process 1 successfully got the mutex 
Child 0 completed
process 0 unable to lock mutex. 
Child 0 completed

ここで、サブプロセス0と1は必ずしも前にあるわけではありません。しかし、ロックを取得できない人が常に存在します。ここでの SyncMutex::lock(int $millisecond) のパラメータはミリ秒で、ブロック期間を表し、-1 は無制限のブロックを意味します。

同期モジュールの読み取り/書き込みロック:
SyncReaderWriter のメソッドは同様で、readlock、readunlock、writelock、writeunlock がペアで出現する可能性があります。これは Mutex コードと一致している必要があります。ロックを交換します。

同期モジュールのイベント:
golang の Cond に似ており、wait() がブロックし、fire() が Event によってブロックされたプロセスを起動します。 Cond を紹介する良い記事があります。Cond がロックの固定的な使用法であることがわかります。 SyncEvent についても同様です。
PHP ドキュメントの例は、fire() メソッドが Web アプリケーションで使用されているようであることを示しています。

for($i=0; $i<3; $i++){
  $pid = pcntl_fork();
  if($pid <0){
    die("fork failed");
  }elseif ($pid>0){
    //echo "parent process \n";
  }else{
    echo "child process {$i} is born. \n";
    switch ($i) {
    case 0:
      wait();
      break;
    case 1:
      wait();
      break;
    case 2:
      sleep(1);
      fire();
      break;
    }
  }
}

while (pcntl_waitpid(0, $status) != -1) { 
  $status = pcntl_wexitstatus($status); 
  echo "Child $status completed\n"; 
}

function wait(){
  $event = new SyncEvent("UniqueName");
  echo "before waiting. \n";
  $event->wait();
  echo "after waiting. \n";
  exit();
}

function fire(){
  $event = new SyncEvent("UniqueName");
  $event->fire();
  exit();
}
のテストコード

ここでは意図的に書かれた fire() が 1 つ少ないため、プログラムはブロックされます。これは、 fire() が一度に 1 つのプロセスのみを起動することを証明しています。

pthreads モジュール
ミューテックスのロックとロック解除:

関数:


pthread_mutex_lock (mutex) 
pthread_mutex_trylock (mutex) 
pthread_mutex_unlock (mutex)

使用法:

ミューテックスが別のスレッドによってロックされている場合、スレッドは pthread_mutex_lock() 関数を使用して指定されたミューテックス変数をロックします。 、この呼び出しは、ミューテックスのロックが解除されるまでスレッドをブロックします。
pthread_mutex_trylock() はミューテックスをロックしようとしますが、ミューテックスがすでにロックされている場合、ルーチンはすぐに「ビジー」エラー コードを返します。このルーチンは pthread_mutex_trylock() で役立つ場合があります。ミューテックスがロックされている場合、プログラムはすぐに戻り、ビジー エラー値を返します。この機能は、優先度変更時のデッドロックを防ぐのに役立ちます。スレッドは pthread_mutex_unlock() を使用して、スレッドが占有しているミューテックスのロックを解除できます。この関数は、1 つのスレッドが保護されたデータの使用を完了し、他のスレッドが保護されたデータを処理するためにミューテックスを取得する必要があるときに呼び出すことができます。次の状況が発生した場合、エラーが発生します。 ミューテックスがロック解除されている。 ミューテックスが別のスレッドによって占有されている。これは、参加しているスレッドの「紳士協定」である。コードを記述するときは、必ずミューテックスを正しくロックおよびロック解除してください。

Q: 同じロックされたミューテックスを待機しているスレッドが複数あります。ミューテックスがロック解除されると、どのスレッドが最初にミューテックスをロックしますか?

A: スレッドが優先スケジューリング メカニズムを使用しない限り、スレッドはシステム スケジューラによって割り当てられ、どのスレッドが最初にミューテックスをロックするかはランダムです。
  • #include<stdlib.h> 
    #include<stdio.h> 
    #include<unistd.h> 
    #include<pthread.h> 
    
    typedef struct ct_sum 
    { 
      int sum; 
      pthread_mutex_t lock; 
    }ct_sum; 
    
    void * add1(void *cnt) 
    {    
      pthread_mutex_lock(&(((ct_sum*)cnt)->lock)); 
      for(int i=0; i < 50; i++) 
      {
        (*(ct_sum*)cnt).sum += i;   
      } 
      pthread_mutex_unlock(&(((ct_sum*)cnt)->lock)); 
      pthread_exit(NULL); 
      return 0; 
    } 
    void * add2(void *cnt) 
    {    
      pthread_mutex_lock(&(((ct_sum*)cnt)->lock)); 
      for(int i=50; i<101; i++) 
      {  
         (*(ct_sum*)cnt).sum += i;  
      } 
      pthread_mutex_unlock(&(((ct_sum*)cnt)->lock)); 
      pthread_exit(NULL); 
      return 0; 
    } 
     
    int main(void) 
    {
      pthread_t ptid1, ptid2; 
      ct_sum cnt; 
      pthread_mutex_init(&(cnt.lock), NULL); 
      cnt.sum=0; 
     
      pthread_create(&ptid1, NULL, add1, &cnt); 
      pthread_create(&ptid2, NULL, add2, &cnt); 
      
      pthread_join(ptid1,NULL); 
      pthread_join(ptid2,NULL);
    
      printf("sum %d\n", cnt.sum);
      pthread_mutex_destroy(&(cnt.lock)); 
    
      return 0; 
    }

    信号量
    sync模块中的信号量:
    SyncSemaphore文档中显示,它和Mutex的不同之处,在于Semaphore一次可以被多个进程(或线程)得到,而Mutex一次只能被一个得到。所以在SyncSemaphore的构造函数中,有一个参数指定信号量可以被多少进程得到。
    public SyncSemaphore::__construct ([ string $name [, integer $initialval [, bool $autounlock ]]] ) 就是这个$initialval (initial value)


    $lock = new SyncSemaphore("UniqueName", 2);
    
    for($i=0; $i<2; $i++){
      $pid = pcntl_fork();
      if($pid <0){
        die("fork failed");
      }elseif ($pid>0){
        echo "parent process \n";
      }else{
        echo "child process {$i} is born. \n";
        obtainLock($lock, $i);
      }
    }
    
    while (pcntl_waitpid(0, $status) != -1) { 
      $status = pcntl_wexitstatus($status); 
      echo "Child $status completed\n"; 
    }
    
    function obtainLock ($lock, $i){
      echo "process {$i} is getting the lock \n";
      $res = $lock->lock(200);
      sleep(1);
      if (!$res){
        echo "process {$i} unable to lock lock. \n";
      }else{
        echo "process {$i} successfully got the lock \n";
        $lock->unlock();
      }
      exit();
    }

    这时候两个进程都能得到锁。

    • sysvsem模块中的信号量

    • sem_get 创建信号量

    • sem_remove 删除信号量(一般不用)

    • sem_acquire 请求得到信号量

    • sem_release 释放信号量。和 sem_acquire 成对使用。


    $key = ftok(&#39;/tmp&#39;, &#39;c&#39;);
    
    $sem = sem_get($key);
    
    for($i=0; $i<2; $i++){
      $pid = pcntl_fork();
      if($pid <0){
        die("fork failed");
      }elseif ($pid>0){
        //echo "parent process \n";
      }else{
        echo "child process {$i} is born. \n";
        obtainLock($sem, $i);
      }
    }
    
    while (pcntl_waitpid(0, $status) != -1) { 
      $status = pcntl_wexitstatus($status); 
      echo "Child $status completed\n"; 
    }
    sem_remove($sem); // finally remove the sem
    
    function obtainLock ($sem, $i){
      echo "process {$i} is getting the sem \n";
      $res = sem_acquire($sem, true);
      sleep(1);
      if (!$res){
        echo "process {$i} unable to get sem. \n";
      }else{
        echo "process {$i} successfully got the sem \n";
        sem_release($sem);
      }
      exit();
    }

    这里有一个问题,sem_acquire()第二个参数$nowait默认为false,阻塞。我设为了true,如果得到锁失败,那么后面的sem_release会报警告 PHP Warning:  sem_release(): SysV semaphore 4 (key 0x63000081) is not currently acquired in /home/jason/sysvsem.php on line 33, 所以这里的release操作必须放在得到锁的情况下执行,前面的几个例子中没有这个问题,没得到锁执行release也不会报错。当然最好还是成对出现,确保得到锁的情况下再release。
    此外,ftok这个方法的参数有必要说明下,第一个 必须是existing, accessable的文件, 一般使用项目中的文件,第二个是单字符字符串。返回一个int。

    输出为


    parent process 
    parent process 
    child process 1 is born. 
    process 1 is getting the mutex 
    child process 0 is born. 
    process 0 is getting the mutex 
    process 1 successfully got the mutex 
    Child 0 completed
    process 0 unable to lock mutex. 
    Child 0 completed

    相关推荐:

    简单介绍PHP 文件锁与进程锁

    php 文件读取系列方法详解

    简单谈谈 php 文件锁

以上がPHP のファイル ロック、ミューテックス ロック、読み取り/書き込みロックの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。