PHP 网站中的线程锁使用指南

在 PHP 网站开发中,线程锁(或更准确地说,进程锁)用于解决并发访问时的资源竞争问题。由于 PHP 通常是多进程运行的(如 PHP-FPM),而不是真正的多线程,所以我们主要讨论进程间锁的实现。

1. 文件锁 (flock)


php

<?php
// 获取锁文件路径
$lockFile = __DIR__ . '

// 打开锁文件(如果不存在会自动创建)
$lockHandle = fopen($lockFile, 'w+');
if (!$lockHandle) {
    die('无法创建锁文件');
}

// 尝试获取独占锁
if (flock($lockHandle, LOCK_EX)) {
    try {
        // 这里放置需要加锁的代码
        // 例如:修改共享资源、写入文件、更新数据库等
        file_put_contents(__DIR__ . '/temp/counter.txt', 
            intval(file_get_contents(__DIR__ . '/temp/counter.txt')) + 1);
        
        echo "操作成功完成\n";
    } finally {
        // 释放锁
        flock($lockHandle, LOCK_UN);
    }
} else {
    echo "无法获取锁,另一个进程可能正在执行操作\n";
}

// 关闭文件句柄
fclose($lockHandle);
?>

2. Redis 分布式锁

对于分布式系统或多服务器环境,可以使用 Redis 实现分布式锁:


php

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$lockKey = 'resource_lock';
$lockTimeout = 10; // 锁超时时间(秒)
$requestId = uniqid(); // 唯一标识当前请求

// 尝试获取锁
$isLocked = $redis->set($lockKey, $requestId, ['nx', 'ex' => $lockTimeout]);

if ($isLocked) {
    try {
        // 获取锁成功,执行业务逻辑
        echo "获取锁成功,执行业务逻辑\n";
        // 模拟业务处理
        sleep(5);
    } finally {
        // 释放锁(使用Lua脚本确保原子性)
        $lua = <<<LUA
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end
LUA;
        $redis->eval($lua, [$lockKey, $requestId], 1);
    }
} else {
    echo "获取锁失败,资源正被占用\n";
}
?>

3. MySQL 锁(GET_LOCK/RELEASE_LOCK)


php

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

$lockName = 'inventory_lock';
$timeout = 5; // 等待锁的超时时间(秒)

try {
    // 尝试获取锁
    $stmt = $pdo->query("SELECT GET_LOCK('$lockName', $timeout)");
    $result = $stmt->fetchColumn();
    
    if ($result == 1) {
        try {
            // 获取锁成功,执行业务逻辑
            echo "获取数据库锁成功\n";
            
            // 示例:更新库存
            $pdo->exec("UPDATE products SET stock = stock - 1 WHERE id = 123");
            
        } finally {
            // 释放锁
            $pdo->query("SELECT RELEASE_LOCK('$lockName')");
        }
    } else {
        echo "获取数据库锁失败或超时\n";
    }
} catch (PDOException $e) {
    echo "数据库错误: " . $e->getMessage();
}
?>

4. Symfony Lock 组件(推荐用于框架项目)

如果你使用 Symfony 框架,可以使用其内置的 Lock 组件:


php

<?php
// 安装: composer require symfony/lock

use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\RedisStore;

// 文件锁方式
$store = new FlockStore();
// 或者Redis方式
// $redis = new \Predis\Client();
// $store = new RedisStore($redis);

$factory = new LockFactory($store);
$lock = $factory->createLock('inventory-update');

try {
    if ($lock->acquire()) {
        try {
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐