PHP实现进程锁与线程同步:文件锁、互斥锁及信号量实战

许南星
许南星
发布时间:2026-10-13 | 4人浏览过
原创

在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进程同步的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表