2017-12-29 10:20:53 1352瀏覽
今天扣丁學(xué)堂給大家主要介紹了PHP開(kāi)發(fā)程序中的文件鎖、互斥鎖、讀寫(xiě)鎖使用技巧解析,其中重點(diǎn)講解了sync模塊和pthreads模塊中的使用實(shí)例,需要的朋友可以參考下,下面我們一起來(lái)看一下吧。
文件鎖
全名叫advisoryfilelock,書(shū)中有提及。這類(lèi)鎖比較常見(jiàn),例如mysql,php-fpm啟動(dòng)之后都會(huì)有一個(gè)pid文件記錄了進(jìn)程id,這個(gè)文件就是文件鎖。
這個(gè)鎖可以防止重復(fù)運(yùn)行一個(gè)進(jìn)程,例如在使用crontab時(shí),限定每一分鐘執(zhí)行一個(gè)任務(wù),但這個(gè)進(jìn)程運(yùn)行時(shí)間可能超過(guò)一分鐘,如果不用進(jìn)程鎖解決沖突的話兩個(gè)進(jìn)程一起執(zhí)行就會(huì)有問(wèn)題。
使用PID文件鎖還有一個(gè)好處,方便進(jìn)程向自己發(fā)停止或者重啟信號(hào)。例如重啟php-fpm的命令為
kill-USR2`cat/usr/local/php/var/run/php-fpm.pid`
發(fā)送USR2信號(hào)給pid文件記錄的進(jìn)程,信號(hào)屬于進(jìn)程通信,會(huì)另開(kāi)一個(gè)篇幅。
php的接口為flock,文檔比較詳細(xì)。先看一下定義,boolflock(resource$handle,int$operation[,int&$wouldblock]).
$handle是文件系統(tǒng)指針,是典型地由fopen()創(chuàng)建的resource(資源)。這就意味著使用flock必須打開(kāi)一個(gè)文件。
$operation是操作類(lèi)型。
&$wouldblock如果鎖是阻塞的,那么這個(gè)變量會(huì)設(shè)為1.
需要注意的是,這個(gè)函數(shù)默認(rèn)是阻塞的,如果想非阻塞可以在operation加一個(gè)bitmaskLOCK_NB.接下來(lái)測(cè)試一下。
$pid_file="/tmp/process.pid";
$pid=posix_getpid();
$fp=fopen($pid_file,'w+');
if(flock($fp,LOCK_EX|LOCK_NB)){
echo"gotthelock\n";
ftruncate($fp,0);//truncatefile
fwrite($fp,$pid);
fflush($fp);//flushoutputbeforereleasingthelock
sleep(300);//longrunningprocess
flock($fp,LOCK_UN);//釋放鎖定
}else{
echo"Cannotgetpidlock.Theprocessisalreadyup\n";
}
fclose($fp);
保存為process.php,運(yùn)行phpprocess.php&,此時(shí)再次運(yùn)行phpprocess.php,就可以看到錯(cuò)誤提示。flock也有共享鎖,LOCK_SH.
互斥鎖和讀寫(xiě)鎖
sync模塊中的Mutex:
Mutex是一個(gè)組合詞,mutualexclusion。用pecl安裝一下sync模塊,peclinstallsync。文檔中的SyncMutex只有兩個(gè)方法,lock和unlock,我們就直接上代碼測(cè)試吧。沒(méi)有用IDE寫(xiě),所以cs異常丑陋,請(qǐng)無(wú)視。
$mutex=newSyncMutex("UniqueName");
for($i=0;$i<2;$i++){
$pid=pcntl_fork();
if($pid<0){
die("forkfailed");
}elseif($pid>0){
echo"parentprocess\n";
}else{
echo"childprocess{$i}isborn.\n";
obtainLock($mutex,$i);
}
}
while(pcntl_waitpid(0,$status)!=-1){
$status=pcntl_wexitstatus($status);
echo"Child$statuscompleted\n";
}
functionobtainLock($mutex,$i){
echo"process{$i}isgettingthemutex\n";
$res=$mutex->lock(200);
sleep(1);
if(!$res){
echo"process{$i}unabletolockmutex.\n";
}else{
echo"process{$i}successfullygotthemutex\n";
$mutex->unlock();
}
exit();
}
保存為mutex.php,runphpmutex.php,outputis
parentprocess
parentprocess
childprocess1isborn.
process1isgettingthemutex
childprocess0isborn.
process0isgettingthemutex
process1successfullygotthemutex
Child0completed
process0unabletolockmutex.
Child0completed
這里子進(jìn)程0和1不一定誰(shuí)在前面。但是總有一個(gè)得不到鎖。這里SyncMutex::lock(int$millisecond)的參數(shù)是millisecond,代表阻塞的時(shí)長(zhǎng),-1為無(wú)限阻塞。
sync模塊中的讀寫(xiě)鎖:
SyncReaderWriter的方法類(lèi)似,readlock,readunlock,writelock,writeunlock,成對(duì)出現(xiàn)即可,沒(méi)有寫(xiě)測(cè)試代碼,應(yīng)該和Mutex的代碼一致,把鎖替換一下就可以。
sync模塊中的Event:
感覺(jué)和golang中的Cond比較像,wait()阻塞,fire()喚醒Event阻塞的一個(gè)進(jìn)程。有一篇好文介紹了Cond,可以看出Cond就是鎖的一種固定用法。SyncEvent也一樣。
php文檔中的例子顯示,fire()方法貌似可以用在web應(yīng)用中。
上測(cè)試代碼
for($i=0;$i<3;$i++){
$pid=pcntl_fork();
if($pid<0){
die("forkfailed");
}elseif($pid>0){
//echo"parentprocess\n";
}else{
echo"childprocess{$i}isborn.\n";
switch($i){
case0:
wait();
break;
case1:
wait();
break;
case2:
sleep(1);
fire();
break;
}
}
}
while(pcntl_waitpid(0,$status)!=-1){
$status=pcntl_wexitstatus($status);
echo"Child$statuscompleted\n";
}
functionwait(){
$event=newSyncEvent("UniqueName");
echo"beforewaiting.\n";
$event->wait();
echo"afterwaiting.\n";
exit();
}
functionfire(){
$event=newSyncEvent("UniqueName");
$event->fire();
exit();
}
這里故意少寫(xiě)一個(gè)fire(),所以程序會(huì)阻塞,證明了fire()一次只喚醒一個(gè)進(jìn)程。
pthreads模塊
鎖定和解鎖互斥量:
函數(shù):
pthread_mutex_lock(mutex)
pthread_mutex_trylock(mutex)
pthread_mutex_unlock(mutex)
用法:
線程用pthread_mutex_lock()函數(shù)去鎖定指定的mutex變量,若該mutex已經(jīng)被另外一個(gè)線程鎖定了,該調(diào)用將會(huì)阻塞線程直到mutex被解鎖。
pthread_mutex_trylock()willattempttolockamutex.However,ifthemutexisalreadylocked,theroutinewillreturnimmediatelywitha"busy"errorcode.Thisroutinemaybeusefulinpthread_mutex_trylock().
嘗試著去鎖定一個(gè)互斥量,然而,若互斥量已被鎖定,程序會(huì)立刻返回并返回一個(gè)忙錯(cuò)誤值。該函數(shù)在優(yōu)先級(jí)改變情況下阻止死鎖是非常有用的。線程可以用pthread_mutex_unlock()解鎖自己占用的互斥量。在一個(gè)線程完成對(duì)保護(hù)數(shù)據(jù)的使用,而其它線程要獲得互斥量在保護(hù)數(shù)據(jù)上工作時(shí),可以調(diào)用該函數(shù)。若有一下情形則會(huì)發(fā)生錯(cuò)誤:
互斥量已經(jīng)被解鎖
互斥量被另一個(gè)線程占用
互斥量并沒(méi)有多么“神奇”的,實(shí)際上,它們就是參與的線程的“君子約定”。寫(xiě)代碼時(shí)要確信正確地鎖定,解鎖互斥量。
Q:有多個(gè)線程等待同一個(gè)鎖定的互斥量,當(dāng)互斥量被解鎖后,那個(gè)線程會(huì)第一個(gè)鎖定互斥量?
A:除非線程使用了優(yōu)先級(jí)調(diào)度機(jī)制,否則,線程會(huì)被系統(tǒng)調(diào)度器去分配,那個(gè)線程會(huì)第一個(gè)鎖定互斥量是隨機(jī)的。
#include
#include
#include
#include
typedefstructct_sum
{
intsum;
pthread_mutex_tlock;
}ct_sum;
void*add1(void*cnt)
{
pthread_mutex_lock(&(((ct_sum*)cnt)->lock));
for(inti=0;i<50;i++)
{
(*(ct_sum*)cnt).sum+=i;
}
pthread_mutex_unlock(&(((ct_sum*)cnt)->lock));
pthread_exit(NULL);
return0;
}
void*add2(void*cnt)
{
pthread_mutex_lock(&(((ct_sum*)cnt)->lock));
for(inti=50;i<101;i++)
{
(*(ct_sum*)cnt).sum+=i;
}
pthread_mutex_unlock(&(((ct_sum*)cnt)->lock));
pthread_exit(NULL);
return0;
}
intmain(void)
{
pthread_tptid1,ptid2;
ct_sumcnt;
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));
return0;
}
信號(hào)量
sync模塊中的信號(hào)量:
SyncSemaphore文檔中顯示,它和Mutex的不同之處,在于Semaphore一次可以被多個(gè)進(jìn)程(或線程)得到,而Mutex一次只能被一個(gè)得到。所以在SyncSemaphore的構(gòu)造函數(shù)中,有一個(gè)參數(shù)指定信號(hào)量可以被多少進(jìn)程得到。
publicSyncSemaphore::__construct([string$name[,integer$initialval[,bool$autounlock]]])就是這個(gè)$initialval(initialvalue)
$lock=newSyncSemaphore("UniqueName",2);
for($i=0;$i<2;$i++){
$pid=pcntl_fork();
if($pid<0){
die("forkfailed");
}elseif($pid>0){
echo"parentprocess\n";
}else{
echo"childprocess{$i}isborn.\n";
obtainLock($lock,$i);
}
}
while(pcntl_waitpid(0,$status)!=-1){
$status=pcntl_wexitstatus($status);
echo"Child$statuscompleted\n";
}
functionobtainLock($lock,$i){
echo"process{$i}isgettingthelock\n";
$res=$lock->lock(200);
sleep(1);
if(!$res){
echo"process{$i}unabletolocklock.\n";
}else{
echo"process{$i}successfullygotthelock\n";
$lock->unlock();
}
exit();
}
這時(shí)候兩個(gè)進(jìn)程都能得到鎖。
sysvsem模塊中的信號(hào)量
sem_get創(chuàng)建信號(hào)量
sem_remove刪除信號(hào)量(一般不用)
sem_acquire請(qǐng)求得到信號(hào)量
sem_release釋放信號(hào)量。和sem_acquire成對(duì)使用。
$key=ftok('/tmp','c');
$sem=sem_get($key);
for($i=0;$i<2;$i++){
$pid=pcntl_fork();
if($pid<0){
die("forkfailed");
}elseif($pid>0){
//echo"parentprocess\n";
}else{
echo"childprocess{$i}isborn.\n";
obtainLock($sem,$i);
}
}
while(pcntl_waitpid(0,$status)!=-1){
$status=pcntl_wexitstatus($status);
echo"Child$statuscompleted\n";
}
sem_remove($sem);//finallyremovethesem
functionobtainLock($sem,$i){
echo"process{$i}isgettingthesem\n";
$res=sem_acquire($sem,true);
sleep(1);
if(!$res){
echo"process{$i}unabletogetsem.\n";
}else{
echo"process{$i}successfullygotthesem\n";
sem_release($sem);
}
exit();
}
這里有一個(gè)問(wèn)題,sem_acquire()第二個(gè)參數(shù)$nowait默認(rèn)為false,阻塞。我設(shè)為了true,如果得到鎖失敗,那么后面的sem_release會(huì)報(bào)警告PHPWarning:sem_release():SysVsemaphore4(key0x63000081)isnotcurrentlyacquiredin/home/jason/sysvsem.phponline33,所以這里的release操作必須放在得到鎖的情況下執(zhí)行,前面的幾個(gè)例子中沒(méi)有這個(gè)問(wèn)題,沒(méi)得到鎖執(zhí)行release也不會(huì)報(bào)錯(cuò)。當(dāng)然最好還是成對(duì)出現(xiàn),確保得到鎖的情況下再release。
此外,ftok這個(gè)方法的參數(shù)有必要說(shuō)明下,第一個(gè)必須是existing,accessable的文件,一般使用項(xiàng)目中的文件,第二個(gè)是單字符字符串。返回一個(gè)int。
輸出為
parentprocess
parentprocess
childprocess1isborn.
process1isgettingthemutex
childprocess0isborn.
process0isgettingthemutex
process1successfullygotthemutex
Child0completed
process0unabletolockmutex.
Child0completed
以上就是扣丁學(xué)堂PHP視頻教程中關(guān)于文件鎖、互斥鎖、讀寫(xiě)鎖的詳解,最后想要學(xué)習(xí)PHP開(kāi)發(fā)技術(shù)的小伙伴不要猶豫了,扣丁學(xué)堂是PHP培訓(xùn)技術(shù)的最佳選擇,想要PHP視頻教程的小伙伴現(xiàn)在就聯(lián)系我們的咨詢(xún)老師領(lǐng)取吧,機(jī)會(huì)總是留給有準(zhǔn)備的人的!扣丁學(xué)堂PHP開(kāi)發(fā)工程師技術(shù)交流群:374332265。
關(guān)注微信公眾號(hào)獲取更多的學(xué)習(xí)資料
查看更多關(guān)于“php培訓(xùn)資訊”的相關(guān)文章>>