PHP文件锁怎么用

wen PHP项目 3

本文目录导读:

PHP文件锁怎么用

  1. 基本用法
  2. 锁类型详解
  3. 实用场景示例
  4. 注意事项
  5. 性能优化建议

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;
});
?>

性能优化建议

  1. 尽量减少锁持有时间:只锁定必要的操作
  2. 合理使用锁类型:读操作使用 LOCK_SH,写操作使用 LOCK_EX
  3. 避免死锁:始终按照相同的顺序获取多个锁
  4. 使用非阻塞模式:在可能等待很长时间的场景使用 LOCK_NB

文件锁是处理并发问题的基本工具,但在高并发场景下,建议考虑使用数据库或 Redis 等专业的并发控制方案。

抱歉,评论功能暂时关闭!