PHP flock()函数
flock()函数用于对文件进行锁定操作,可以避免多个进程同时访问同一个文件时产生的冲突。
语法
bool flock ( resource $handle , int $operation [, int &$wouldblock ] )
参数说明
| 参数 |
描述 |
handle |
文件系统指针,通常是使用fopen()创建的资源 |
operation |
锁定类型,可以是以下常量之一:
LOCK_SH - 共享锁(读取锁)
LOCK_EX - 独占锁(写入锁)
LOCK_UN - 释放锁
LOCK_NB - 非阻塞模式(与上述锁定类型组合使用)
|
$wouldblock |
可选参数。如果设置为1,表示当锁不可用时,flock()会阻塞(默认行为) |
锁定类型常量详解
| 常量 |
值 |
描述 |
应用场景 |
LOCK_SH |
1 |
共享锁(读取锁) |
多个进程可以同时获取共享锁进行读取操作 |
LOCK_EX |
2 |
独占锁(写入锁) |
只有一个进程可以获取独占锁进行写入操作 |
LOCK_UN |
3 |
释放锁 |
释放当前进程持有的锁 |
LOCK_NB |
4 |
非阻塞模式 |
与LOCK_SH或LOCK_EX组合使用,避免阻塞等待 |
示例代码
示例1:基本的文件锁定与写入
<?php
// 以读写方式打开文件(如果不存在则创建)
$fp = fopen("counter.txt", "c+");
if (flock($fp, LOCK_EX)) { // 获取独占锁(写入锁)
// 读取当前计数
$count = (int)fgets($fp);
// 增加计数
$count++;
// 回到文件开头
fseek($fp, 0);
// 写入新的计数值
fwrite($fp, $count);
// 截断文件(确保不会有多余内容)
ftruncate($fp, ftell($fp));
// 释放锁
flock($fp, LOCK_UN);
echo "当前计数: " . $count;
} else {
echo "无法获取文件锁";
}
// 关闭文件
fclose($fp);
?>
示例2:非阻塞锁定
<?php
$fp = fopen("data.txt", "c+");
// 尝试以非阻塞方式获取独占锁
if (flock($fp, LOCK_EX | LOCK_NB)) {
echo "成功获取锁,正在写入数据...<br>";
// 模拟长时间操作
sleep(2);
fwrite($fp, "Data written at: " . date('Y-m-d H:i:s'));
flock($fp, LOCK_UN);
echo "写入完成,已释放锁";
} else {
echo "无法立即获取锁,文件可能正被其他进程使用";
echo "<br>你可以选择:";
echo "<br>1. 等待重试";
echo "<br>2. 执行其他操作";
echo "<br>3. 给用户显示繁忙信息";
}
fclose($fp);
?>
示例3:共享锁(多进程同时读取)
<?php
// 模拟多个进程同时读取文件
$fp = fopen("readonly_data.txt", "r");
if (flock($fp, LOCK_SH)) { // 获取共享锁
echo "进程 " . getmypid() . " 获取了共享锁<br>";
// 多个进程可以同时持有共享锁
$content = fread($fp, filesize("readonly_data.txt"));
echo "读取到的内容长度: " . strlen($content) . " 字节<br>";
// 模拟读取操作耗时
sleep(1);
flock($fp, LOCK_UN);
echo "进程 " . getmypid() . " 已释放锁";
} else {
echo "无法获取共享锁";
}
fclose($fp);
?>
示例4:锁定的实用类封装
<?php
class FileLocker {
private $fp;
private $filename;
public function __construct($filename) {
$this->filename = $filename;
$this->fp = fopen($filename, "c+");
if (!$this->fp) {
throw new Exception("无法打开文件: " . $filename);
}
}
/**
* 获取独占锁
*/
public function lockExclusive($blocking = true) {
$operation = LOCK_EX;
if (!$blocking) {
$operation |= LOCK_NB;
}
return flock($this->fp, $operation);
}
/**
* 获取共享锁
*/
public function lockShared($blocking = true) {
$operation = LOCK_SH;
if (!$blocking) {
$operation |= LOCK_NB;
}
return flock($this->fp, $operation);
}
/**
* 释放锁
*/
public function unlock() {
return flock($this->fp, LOCK_UN);
}
/**
* 读取文件内容(带锁保护)
*/
public function readWithLock() {
if ($this->lockShared()) {
fseek($this->fp, 0);
$content = stream_get_contents($this->fp);
$this->unlock();
return $content;
}
return false;
}
/**
* 写入文件内容(带锁保护)
*/
public function writeWithLock($content) {
if ($this->lockExclusive()) {
fseek($this->fp, 0);
fwrite($this->fp, $content);
ftruncate($this->fp, ftell($this->fp));
$this->unlock();
return true;
}
return false;
}
public function __destruct() {
if ($this->fp) {
fclose($this->fp);
}
}
}
// 使用示例
try {
$locker = new FileLocker("data.txt");
// 读取数据
$content = $locker->readWithLock();
echo "读取到的内容: " . htmlspecialchars($content) . "<br>";
// 写入数据
$newContent = "更新后的数据: " . date('Y-m-d H:i:s');
if ($locker->writeWithLock($newContent)) {
echo "数据写入成功";
} else {
echo "数据写入失败";
}
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
?>
注意事项
重要提示:
- 锁是基于进程的:flock()锁是进程级别的,同一个进程内的多个线程可以同时操作已锁定的文件
- 锁与文件句柄关联:锁与文件句柄(resource)关联,而不是与文件关联。关闭文件句柄或脚本结束时会自动释放锁
- 锁的继承:通过fork()创建的子进程不会继承父进程的文件锁
- NFS文件系统:在NFS或其他网络文件系统上,flock()可能无法正常工作
- Windows系统差异:在Windows上,flock()使用强制性锁,而在Unix-like系统上通常使用建议性锁
- 锁的粒度:flock()锁定整个文件,无法锁定文件的特定部分
- 死锁风险:不当的锁定顺序可能导致死锁,特别是在多个文件需要锁定的时候
示例5:避免死锁的锁定策略
<?php
// 锁定多个文件时的死锁避免策略
function safeMultiFileWrite($files, $data) {
$handles = [];
$locked = [];
try {
// 第一阶段:打开所有文件
foreach ($files as $filename) {
$fp = fopen($filename, "c+");
if (!$fp) {
throw new Exception("无法打开文件: $filename");
}
$handles[$filename] = $fp;
}
// 第二阶段:按照固定顺序获取锁(避免死锁的关键)
$sortedFiles = array_keys($handles);
sort($sortedFiles); // 按字母顺序排序,确保所有进程使用相同的锁定顺序
foreach ($sortedFiles as $filename) {
$fp = $handles[$filename];
// 使用非阻塞模式尝试获取锁
if (!flock($fp, LOCK_EX | LOCK_NB)) {
// 如果获取失败,释放所有已获取的锁
foreach ($locked as $lockedFile) {
flock($handles[$lockedFile], LOCK_UN);
}
throw new Exception("无法获取文件 $filename 的锁,可能发生死锁风险");
}
$locked[] = $filename;
}
// 第三阶段:所有锁都已获取,执行操作
foreach ($handles as $filename => $fp) {
fseek($fp, 0);
fwrite($fp, $data[$filename]);
ftruncate($fp, ftell($fp));
}
// 第四阶段:释放所有锁
foreach ($locked as $filename) {
flock($handles[$filename], LOCK_UN);
}
return true;
} catch (Exception $e) {
// 清理:关闭所有文件句柄
foreach ($handles as $fp) {
fclose($fp);
}
return false;
}
}
// 使用示例
$files = ["file1.txt", "file2.txt", "file3.txt"];
$data = [
"file1.txt" => "Data for file1",
"file2.txt" => "Data for file2",
"file3.txt" => "Data for file3"
];
if (safeMultiFileWrite($files, $data)) {
echo "所有文件写入成功";
} else {
echo "文件写入失败";
}
?>
相关函数
fopen() - 打开文件或URL
fclose() - 关闭一个已打开的文件指针
ftruncate() - 将文件截断到给定的长度
fseek() - 在文件指针中定位
sem_acquire() - 获取信号量(System V信号量)
shmop_open() - 创建或打开共享内存块
典型应用场景
- 访问计数器:多个用户同时访问时的计数器递增
- 日志文件写入:多进程同时写入日志文件
- 配置文件更新:防止配置文件在读取时被修改
- 缓存文件生成:防止多个进程同时生成相同的缓存文件
- 临时文件操作:确保临时文件操作的原子性