PHP編程中的鎖
最近看了《理解Linux進程》這本開源書,鏈接。該書描述了linux中的進程概念,對鎖和進程間通信(IPC)有一些總結(jié)。不過該書的描述語言是golang, 平時用的比較少,就想對應概念找找php中的接口。
文件鎖全名叫advisory file lock, 書中有提及。 這類鎖比較常見,例如 mysql, php-fpm 啟動之后都會有一個pid文件記錄了進程id,這個文件就是文件鎖。
這個鎖可以防止重復運行一個進程,例如在使用crontab時,限定每一分鐘執(zhí)行一個任務,但這個進程運行時間可能超過一分鐘,如果不用進程鎖解決沖突的話兩個進程一起執(zhí)行就會有問題。
使用PID文件鎖還有一個好處,方便進程向自己發(fā)停止或者重啟信號。例如重啟php-fpm的命令為
kill -USR2 `cat /usr/local/php/var/run/php-fpm.pid`
發(fā)送USR2信號給pid文件記錄的進程,信號屬于進程通信,會另開一個篇幅。
php的接口為flock,文檔比較詳細。先看一下定義,bool flock ( resource $handle , int $operation [, int &$wouldblock ] ).
$handle是文件系統(tǒng)指針,是典型地由 fopen() 創(chuàng)建的 resource(資源)。這就意味著使用flock必須打開一個文件。
$operation是操作類型。
&$wouldblock如果鎖是阻塞的,那么這個變量會設為1.
需要注意的是,這個函數(shù)默認是阻塞的,如果想非阻塞可以在 operation 加一個 bitmaskLOCK_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.
互斥鎖和讀寫鎖sync模塊中的MutexMutex是一個組合詞,mutual exclusion。用pecl安裝一下sync模塊,pecl install sync。 文檔中的SyncMutex只有兩個方法,lock 和 unlock, 我們就直接上代碼測試吧。沒有用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 completedn'; }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, runphp mutex.php, output is
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 completedprocess 0 unable to lock mutex. Child 0 completed
這里子進程0和1不一定誰在前面。但是總有一個得不到鎖。這里SyncMutex::lock(int $millisecond)的參數(shù)是 millisecond, 代表阻塞的時長, -1 為無限阻塞。
sync模塊中的讀寫鎖SyncReaderWriter的方法類似,readlock,readunlock,writelock,writeunlock,成對出現(xiàn)即可,沒有寫測試代碼,應該和Mutex的代碼一致,把鎖替換一下就可以。
sync模塊中的Event感覺和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 completedn'; }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(), 所以程序會阻塞,證明了 fire() 一次只喚醒一個進程。
pthreads模塊貌似也看到了Mutex, Cond, Pool. 沒來得及看,看完再補充。
信號量sync模塊中的信號量SyncSemaphore文檔中顯示,它和Mutex的不同之處,在于Semaphore一次可以被多個進程(或線程)得到,而Mutex一次只能被一個得到。所以在SyncSemaphore的構(gòu)造函數(shù)中,有一個參數(shù)指定信號量可以被多少進程得到。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 completedn'; }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創(chuàng)建信號量
sem_remove刪除信號量(一般不用)
sem_acquire請求得到信號量
sem_release釋放信號量。和sem_acquire成對使用。
$key = ftok(’/tmp’, ’c’);$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 completedn'; }sem_remove($sem); // finally remove the semfunction 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()第二個參數(shù)$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操作必須放在得到鎖的情況下執(zhí)行,前面的幾個例子中沒有這個問題,沒得到鎖執(zhí)行release也不會報錯。當然最好還是成對出現(xiàn),確保得到鎖的情況下再release。
此外,ftok這個方法的參數(shù)有必要說明下,第一個 必須是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 completedprocess 0 unable to lock mutex. Child 0 completed
最后,如果文中有錯誤的地方,希望大神指出,幫助一下菜鳥進步,謝謝各位。
相關文章:
1. 一個 2 年 Android 開發(fā)者的 18 條忠告2. Vue實現(xiàn)仿iPhone懸浮球的示例代碼3. js select支持手動輸入功能實現(xiàn)代碼4. vue-drag-chart 拖動/縮放圖表組件的實例代碼5. 什么是Python變量作用域6. Spring的異常重試框架Spring Retry簡單配置操作7. Android 實現(xiàn)徹底退出自己APP 并殺掉所有相關的進程8. PHP正則表達式函數(shù)preg_replace用法實例分析9. Android studio 解決logcat無過濾工具欄的操作10. vue使用moment如何將時間戳轉(zhuǎn)為標準日期時間格式
