最近看了《理解Linux进程》这本开源书,连接。该书描述了linux中的进程概念,对锁和进程间通讯(IPC)有一些总结。不过该书的描述语言是golang, 平时用的比较少,就想对应概念找找php中的接口。php
全名叫 advisory file lock
, 书中有说起。 这类锁比较常见,例如 mysql, php-fpm 启动以后都会有一个pid文件记录了进程id,这个文件就是文件锁。html
这个锁能够防止重复运行一个进程,例如在使用crontab时,限定每一分钟执行一个任务,但这个进程运行时间可能超过一分钟,若是不用进程锁解决冲突的话两个进程一块儿执行就会有问题。mysql
使用PID文件锁还有一个好处,方便进程向本身发中止或者重启信号。例如重启php-fpm的命令为linux
kill -USR2 `cat /usr/local/php/var/run/php-fpm.pid`
发送USR2
信号给pid文件记录的进程,信号属于进程通讯,会另开一个篇幅。golang
php的接口为flock
,文档比较详细。先看一下定义,bool flock ( resource $handle , int $operation [, int &$wouldblock ] )
.web
$handle
是文件系统指针,是典型地由 fopen() 建立的 resource(资源)。这就意味着使用flock必须打开一个文件。sql
$operation
是操做类型。编程
&$wouldblock
若是锁是阻塞的,那么这个变量会设为1.函数
须要注意的是,这个函数默认是阻塞的,若是想非阻塞能够在 operation 加一个 bitmask LOCK_NB
. 接下来测试一下。php-fpm
$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
.
Mutex是一个组合词,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 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
, run php 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 completed process 0 unable to lock mutex. Child 0 completed
这里子进程0和1不必定谁在前面。可是总有一个得不到锁。这里SyncMutex::lock(int $millisecond)
的参数是 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(), 因此程序会阻塞,证实了 fire() 一次只唤醒一个进程。
貌似也看到了Mutex, Cond, Pool. 没来得及看,看完再补充。
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(); }
这时候两个进程都能获得锁。
sem_get
建立信号量
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 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
最后,若是文中有错误的地方,但愿大神指出,帮助一下菜鸟进步,谢谢各位。