PHP实现进程锁与线程同步:文件锁、互斥锁及信号量实战
在PHP高并发或后台脚本场景中,防止任务重复执行和协调多进程/线程访问共享资源是关键挑战。本文将深入解析PHP自带的文件锁机制、`sync`扩展提供的互斥锁与事件对象,以及系统级信号量的具体应用。通过这些工具,开发者可以有效控制执行流,确保数据的原子性与程序的稳定性。
利用文件锁防止进程重复运行
文件锁(Advisory File Lock)是PHP中最基础的同步原语。其典型应用场景包括防止定时任务(如Crontab)重叠执行,以及为守护进程提供PID记录以便发送重启或停止信号。
第1步:使用flock实现排他性锁定
目的:确保同一时刻只有一个进程能获取锁并继续执行,其他进程则立即失败退出,从而避免资源竞争或逻辑错误。
入口:PHP内置函数 flock()。
参数说明:
$handle: 由fopen()创建的资源指针。$operation: 操作类型。LOCK_EX为排他锁(写),LOCK_SH为共享锁(读)。&$wouldblock: 引用传递,若锁被占用且模式为非阻塞,该变量设为1。
关键操作:
$pid_file = "/tmp/process.pid";
$pid = posix_getpid();
$fp = fopen($pid_file, 'w+');
// 尝试以非阻塞模式获取排他锁 (LOCK_NB)
if(flock($fp, LOCK_EX | LOCK_NB)){
echo "got the lock \n";
ftruncate($fp, 0); // 清空原内容
fwrite($fp, $pid); // 写入当前进程ID
fflush($fp); // 确保数据写入磁盘
// 模拟长时间运行的业务逻辑
sleep(300);
flock($fp, LOCK_UN); // 释放锁定
} else {
echo "Cannot get pid lock. The process is already up \n";
}
fclose($fp);
完成状态判断:如果输出 “got the lock”,说明当前获取了控制权;若已存在同类进程,将提示 “The process is already up” 并安全退出。
注意事项:除了用于防重,PID文件还方便外部通过 kill -USR2 $(cat /usr/local/php/var/run/php-fpm.pid) 等命令管理进程生命周期。
Sync扩展:高级进程同步原语
当文件锁不足以应对复杂的进程间通信需求时,pecl install sync 安装的系统级同步模块提供了更强大的工具,包括互斥锁、事件触发器和信号量。
第2步:使用SyncMutex实现互斥访问
目的:保护临界区代码,确保同一时间仅有一个子进程执行关键逻辑。
入口: new SyncMutex("UniqueName") 实例化,配合 pcntl_fork() 生成子进程。
关键操作:
$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";
// 第二个参数为等待毫秒数,-1表示无限等待
$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();
}
完成状态判断:两个子进程中,必定有一个先获得锁成功并打印 “successfully got”,另一个因超时(200ms)未获锁而打印 “unable to lock”。这证明了互斥锁的排他性。
第3步:使用SyncEvent实现进程唤醒
目的:类似于Go语言中的Cond,一个进程阻塞等待,另一个进程通过“发火”(fire)来唤醒它。
入口: SyncEvent 对象,方法包括 wait() 和 fire()。
关键操作:
// ... 分叉循环结构略 ...
switch ($i) {
case 0: // 进程0等待
wait();
break;
case 2: // 进程2延迟后发火
sleep(1);
fire();
break;
}
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() 调用,等待进程将永久阻塞。因此需确保在业务逻辑完成后正确发送唤醒信号。
信号量:允许多并发控制
与Mutex的“独占”不同,信号量(Semaphore)允许同时设定一定数量的并发访问权限。这在控制连接池大小或限制并发下载数量时非常有用。
第4步:配置SyncSemaphore并行访问
目的:允许指定数量的进程同时进入临界区。
入口: new SyncSemaphore("UniqueName", 2),第二个参数 $initialval 即为初始可用槽位数。
关键操作:
$lock = new SyncSemaphore("UniqueName", 2);
// ... 循环分叉 ...
function obtainLock ($lock, $i){
echo "process {$i} is getting the lock \n";
$res = $lock->lock(200);
if ($res){
echo "process {$i} successfully got the lock \n";
$lock->unlock();
}
exit();
}
结果差异:当设置为2时,两个子进程通常都能成功获取锁并打印成功信息,而非像Mutex那样出现竞争失败。
第5步:SysV Semaphore的使用细节
目的:利用System V IPC机制进行跨用户空间的同步。
关键参数:ftok() 需要系统中存在的文件名和单字符项目ID作为密钥。
风险提醒与操作修正:
$key = ftok('/tmp', 'c');
$sem = sem_get($key);
function obtainLock ($sem, $i){
// 注意:$nowait 设为true时,若无法立即获取锁将直接返回false
// 此时绝不能调用sem_release(),否则会报 Warning
$res = sem_acquire($sem, true);
if (!$res){
echo "process {$i} unable to get sem. \n";
return; // 直接返回,不调用release
}
echo "process {$i} successfully got the sem \n";
// 只有在成功acquire后才能release,必须成对出现
sem_release($sem);
exit();
}
完成状态判断:正确使用 `sem_acquire` 和 `sem_release` 配对可避免内存泄漏或警告;务必在获取失败的路径中跳过解锁步骤。
pthreads模块与底层互斥量
虽然原生PCNTL多进程处理较多,但在某些需要常驻内存线程池的场景下,pthreads 扩展提供了底层的 pthread_mutex_lock 等接口。其核心逻辑仍是“加锁->操作->解锁”,并在锁被占用时选择阻塞或立即报错(trylock)。编译此类C扩展时需包含 <pthread.h>,确保资源初始化与销毁(pthread_mutex_destroy)匹配,以防止僵尸锁。
简要总结
PHP开发中选择锁策略取决于并发维度:文件锁适合简单的“防重”和独立进程隔离;sync 扩展的 Mutex/Event/Semaphore 是处理多进程协作的高效手段;SysV Semaphore需注意获取与释放的严谨性;而 pthreads 适用于线程级的精细控制。理解各锁的行为边界,能有效构建稳健的高并发PHP应用。
以上就是PHP程序中的文件锁、互斥锁、读写锁使用技巧解析的详细内容,更多关于PHP进程同步的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













