本文目录导读:

PHP 文件锁主要通过 flock() 函数实现,可以防止多个进程同时修改同一文件导致的数据竞争问题。
基本用法
简单文件锁示例
<?php
// 打开文件
$fp = fopen('data.txt', 'r+');
if ($fp === false) {
die("无法打开文件");
}
// 获取独占锁(写锁)
if (flock($fp, LOCK_EX)) {
// 读取文件内容
$content = file_get_contents('data.txt');
// 执行写入操作
file_put_contents('data.txt', $content . "新数据\n");
// 释放锁
flock($fp, LOCK_UN);
} else {
echo "获取锁失败";
}
fclose($fp);
?>
完整的事务处理示例
<?php
class FileLock {
private $fp;
private $filepath;
public function __construct($filepath) {
$this->filepath = $filepath;
}
public function lock($mode = LOCK_EX) {
$this->fp = fopen($this->filepath, 'c+');
if ($this->fp === false) {
return false;
}
return flock($this->fp, $mode);
}
public function unlock() {
flock($this->fp, LOCK_UN);
fclose($this->fp);
}
}
// 使用示例
$lock = new FileLock('counter.txt');
if ($lock->lock()) {
// 读取当前值
$count = (int)file_get_contents('counter.txt');
// 执行更新
$count++;
// 写入新值
ftruncate($lock->fp, 0); // 清除文件内容
fwrite($lock->fp, $count);
// 释放锁
$lock->unlock();
echo "计数器值: " . $count;
}
?>
锁类型详解
共享锁(读锁)
<?php
// 多个进程可以同时读取
$fp = fopen('data.txt', 'r');
if (flock($fp, LOCK_SH)) {
echo "获得共享锁,可以读取文件\n";
$content = file_get_contents('data.txt');
echo $content;
flock($fp, LOCK_UN);
}
fclose($fp);
?>
独占锁(写锁)
<?php
// 同一时间只有一个进程可以写入
$fp = fopen('data.txt', 'w');
if (flock($fp, LOCK_EX)) {
echo "获得独占锁,阻塞其他写入\n";
// 写入操作
fwrite($fp, "新数据内容");
flock($fp, LOCK_UN);
}
fclose($fp);
?>
非阻塞锁
<?php
$fp = fopen('data.txt', 'r+');
// 非阻塞模式
if (flock($fp, LOCK_EX | LOCK_NB)) {
echo "立即获得锁\n";
// 执行操作
flock($fp, LOCK_UN);
} else {
echo "文件已被锁定,跳过操作\n";
}
fclose($fp);
?>
实用场景示例
计数器应用
<?php
function incrementCounter($file) {
$fp = fopen($file, 'c+');
if (!$fp) return false;
if (flock($fp, LOCK_EX)) {
// 读取当前值
$current = (int)fread($fp, filesize($file) ?: 1);
// 增加计数器
$current++;
// 重置文件指针并写入新值
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, $current);
flock($fp, LOCK_UN);
fclose($fp);
return $current;
}
fclose($fp);
return false;
}
// 多进程并发调用
echo "计数结果: " . incrementCounter('counter.txt');
?>
日志写入器
<?php
class LogWriter {
private $file;
public function __construct($file) {
$this->file = $file;
}
public function write($message) {
$fp = fopen($this->file, 'a');
if ($fp === false) return false;
// 尝试获取锁(最多等待10秒)
$timeout = 10;
$startTime = time();
while (!flock($fp, LOCK_EX)) {
if ((time() - $startTime) > $timeout) {
fclose($fp);
return false;
}
usleep(100000); // 100ms
}
// 写入日志
$logEntry = '[' . date('Y-m-d H:i:s') . '] ' . $message . "\n";
fwrite($fp, $logEntry);
// 释放锁并关闭
flock($fp, LOCK_UN);
fclose($fp);
return true;
}
}
// 使用示例
$logger = new LogWriter('app.log');
$logger->write('用户登录成功');
?>
配置文件更新
<?php
function updateConfig($file, $key, $value) {
$fp = fopen($file, 'r+');
if (!$fp) return false;
if (flock($fp, LOCK_EX)) {
// 读取并解析当前配置
$content = fread($fp, filesize($file));
$config = json_decode($content, true);
// 更新配置
$config[$key] = $value;
// 写回文件
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, json_encode($config));
flock($fp, LOCK_UN);
fclose($fp);
return true;
}
fclose($fp);
return false;
}
// 使用示例
updateConfig('config.json', 'timeout', 30);
?>
注意事项
锁的作用域
<?php
// 注意:锁只在当前文件操作期间有效
function badLockExample() {
$fp = fopen('data.txt', 'r+');
flock($fp, LOCK_EX);
// 其他操作
$content = file_get_contents('data.txt'); // 这个不受锁保护
// 因为 file_get_contents 是独立操作
fclose($fp); // 关闭文件会自动释放锁
}
// 正确的做法
function goodLockExample() {
$fp = fopen('data.txt', 'r+');
flock($fp, LOCK_EX);
// 使用同一文件指针进行读写
rewind($fp);
$content = stream_get_contents($fp);
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, $content . "新数据");
fclose($fp); // 自动释放锁
}
?>
超时处理
<?php
function getLockWithTimeout($fp, $timeout = 5) {
$start = microtime(true);
while (!flock($fp, LOCK_EX | LOCK_NB)) {
if (microtime(true) - $start > $timeout) {
throw new Exception("获取锁超时");
}
usleep(100000); // 等待100ms
}
return true;
}
?>
最佳实践
<?php
class SafeFileOperation {
private $file;
public function __construct($file) {
$this->file = $file;
}
public function safeWrite($callback) {
$fp = fopen($this->file, 'c+');
if (!$fp) throw new Exception("无法打开文件");
try {
if (flock($fp, LOCK_EX)) {
// 执行回调函数
$result = $callback($fp);
// 释放锁
flock($fp, LOCK_UN);
return $result;
}
throw new Exception("无法获取文件锁");
} finally {
fclose($fp);
}
}
}
// 使用示例
$fileOp = new SafeFileOperation('data.txt');
$result = $fileOp->safeWrite(function($fp) {
// 安全的文件操作
$content = stream_get_contents($fp);
rewind($fp);
ftruncate($fp, 0);
fwrite($fp, $content . "新内容");
return true;
});
?>
性能优化建议
- 尽量减少锁持有时间:只锁定必要的操作
- 合理使用锁类型:读操作使用
LOCK_SH,写操作使用LOCK_EX - 避免死锁:始终按照相同的顺序获取多个锁
- 使用非阻塞模式:在可能等待很长时间的场景使用
LOCK_NB
文件锁是处理并发问题的基本工具,但在高并发场景下,建议考虑使用数据库或 Redis 等专业的并发控制方案。