php互斥信号实现
PHP 互斥信号实现
在 PHP 中,实现互斥信号(Mutex,互斥锁)可以通过多种方式完成,主要用于解决多进程或多线程环境下的资源竞争问题。以下是常见的实现方法:
使用文件锁(flock)
文件锁是一种简单的跨进程互斥实现方式,适用于单机多进程场景。
$fp = fopen("/tmp/lockfile", "w+");
if (flock($fp, LOCK_EX)) { // 获取独占锁
// 临界区代码
flock($fp, LOCK_UN); // 释放锁
} else {
echo "无法获取锁";
}
fclose($fp);
- 文件路径需唯一,避免冲突。
LOCK_EX表示独占锁,LOCK_UN释放锁。- 需处理文件打开失败和锁超时情况。
使用 Semaphore 扩展
PHP 的 sysvsem 扩展提供信号量支持,适用于更复杂的同步场景。
$semKey = ftok(__FILE__, 't');
$semId = sem_get($semKey, 1); // 第二个参数表示最大并发数
if (sem_acquire($semId)) { // 获取信号量
// 临界区代码
sem_release($semId); // 释放信号量
}
ftok生成唯一键,sem_get创建信号量。sem_acquire会阻塞直到获取信号量。- 需确保信号量资源释放,避免死锁。
使用 Redis 分布式锁
对于分布式系统,可以通过 Redis 实现跨机器的互斥锁。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lockKey = 'resource_lock';
$token = uniqid();
// 尝试获取锁(SETNX + 过期时间)
if ($redis->set($lockKey, $token, ['NX', 'EX' => 10])) {
try {
// 临界区代码
} finally {
// 确保仅释放自己的锁(Lua 脚本避免误删)
$script = 'if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end';
$redis->eval($script, [$lockKey, $token], 1);
}
}
- 使用
SETNX和过期时间避免死锁。 - 通过唯一 token 确保锁的安全性。
- 需处理 Redis 连接异常和锁续期问题。
使用 Pthreads 扩展(多线程)
PHP 的 pthreads 扩展支持多线程编程,提供内置的互斥锁。
class WorkerThread extends Thread {
private $mutex;
public function __construct(Mutex $mutex) {
$this->mutex = $mutex;
}
public function run() {
Mutex::lock($this->mutex);
// 临界区代码
Mutex::unlock($this->mutex);
}
}
$mutex = Mutex::create();
$thread = new WorkerThread($mutex);
$thread->start();
$thread->join();
Mutex::destroy($mutex);
- 仅适用于 CLI 环境且启用 ZTS(Zend Thread Safety)的 PHP。
- 需安装
pthreads扩展并正确配置 PHP。
注意事项
- 死锁风险:确保锁最终被释放,避免因异常导致的死锁。
- 性能影响:锁的粒度应尽量小,减少临界区代码执行时间。
- 分布式一致性:分布式锁需考虑网络分区和时钟漂移问题。
- 超时机制:为锁设置超时时间,避免长时间阻塞。
根据实际场景选择合适的实现方式,单机环境推荐文件锁或信号量,分布式系统建议使用 Redis 或专业的分布式锁服务。




