在PHP开发的多进程场景中,多个进程同时读写同一个文件是常见需求,但如果没有做并发控制,很容易出现数据被覆盖、文件内容错乱的问题。利用flock函数的独占锁机制,可以有效避免这类问题,保证同一时间只有一个进程能对文件执行写入操作。

flock函数基本说明
flock是PHP内置的文件锁函数,用于对打开的文件句柄加锁或解锁,函数原型如下:
flock(resource $handle, int $operation, int &$wouldblock = null): bool其中$operation参数支持以下几种常用值:
- LOCK_SH:获取共享锁,用于读取文件,多个进程可以同时持有共享锁
- LOCK_EX:获取独占锁,用于写入文件,同一时间只能有一个进程持有独占锁
- LOCK_UN:释放锁
- LOCK_NB:非阻塞模式,加锁失败时不等待直接返回
独占锁实现多进程文件互斥操作
要实现防止多个进程同时操作同一个文件,核心是在写入文件前先获取独占锁,操作完成后释放锁。下面是一个完整的示例代码:
<?php
// 要操作的文件路径
$filePath = '/tmp/test.txt';
// 打开文件,模式为追加写入
$fp = fopen($filePath, 'a');
if (!$fp) {
die('打开文件失败');
}
// 尝试获取独占锁,阻塞模式,直到获取锁才继续往下执行
if (flock($fp, LOCK_EX)) {
// 模拟业务处理耗时
sleep(2);
// 写入内容,这里写入当前进程ID和操作时间
$content = "进程ID:" . getmypid() . ",操作时间:" . date('Y-m-d H:i:s') . PHP_EOL;
fwrite($fp, $content);
// 操作完成后释放锁
flock($fp, LOCK_UN);
echo "进程" . getmypid() . "写入文件成功" . PHP_EOL;
} else {
echo "进程" . getmypid() . "获取文件锁失败" . PHP_EOL;
}
// 关闭文件句柄
fclose($fp);
?>
非阻塞模式的使用场景
如果不想让进程在获取锁时阻塞等待,可以结合LOCK_NB使用非阻塞模式,加锁失败时直接返回,避免进程长时间等待:
<?php
$filePath = '/tmp/test.txt';
$fp = fopen($filePath, 'a');
if (!$fp) {
die('打开文件失败');
}
// 非阻塞模式获取独占锁
if (flock($fp, LOCK_EX | LOCK_NB, $wouldblock)) {
// 获取锁成功,执行写入操作
$content = "非阻塞模式进程ID:" . getmypid() . ",操作时间:" . date('Y-m-d H:i:s') . PHP_EOL;
fwrite($fp, $content);
flock($fp, LOCK_UN);
echo "进程" . getmypid() . "非阻塞模式写入成功" . PHP_EOL;
} else {
// 获取锁失败,直接处理其他逻辑
echo "进程" . getmypid() . "当前文件被占用,无需等待" . PHP_EOL;
}
fclose($fp);
?>
注意事项
使用flock实现文件锁时需要注意以下几点:
- flock是建议性锁,不是强制性锁,需要所有操作文件的进程都遵循加锁逻辑才能生效,如果某个进程不调用flock直接操作文件,依然会出现并发问题
- 文件锁在进程退出或者文件句柄关闭时会自动释放,不需要手动调用释放锁也可以,但建议显式释放锁保证逻辑清晰
- flock的锁是与文件句柄关联的,同一个进程内多次对同一个文件句柄加锁不会阻塞,不同进程的句柄才会互斥
- 如果是NFS等网络文件系统,flock的锁机制可能不生效,需要确认文件系统的锁支持情况
总结
通过flock的独占锁机制,可以简单有效地解决PHP多进程同时操作同一个文件的并发问题,核心逻辑就是在写入前获取LOCK_EX独占锁,操作完成后释放锁。开发者可以根据业务场景选择阻塞或非阻塞模式,同时注意flock的使用限制,保证多进程文件操作的安全性和正确性。