本文目录导读:

在 PHP 中,“抢占” 通常指以下几种不同的场景,你需要根据具体需求选择对应方案:
- 防止并发请求冲突(锁机制)
- 多进程/多线程下的资源竞争(进程锁、共享内存)
- 任务队列的公平调度(消费者抢占)
以下针对最常见的 “防止并发请求导致数据异常” 场景(即“独占锁”)给出具体实现。
场景:防止同一个用户短时间内重复提交订单(防刷)
核心思路:使用文件锁或Redis锁,在临界区加锁。
方案1:文件锁(flock)—— 适用于单机、小并发
<?php
$lockFile = '/tmp/order_lock_' . $userId . '.lock';
$fp = fopen($lockFile, 'w+');
if (flock($fp, LOCK_EX | LOCK_NB)) { // 获取独占锁,不阻塞
try {
// 执行业务逻辑(如创建订单)
echo "业务处理中...";
sleep(2); // 模拟耗时操作
} finally {
flock($fp, LOCK_UN); // 释放锁
}
} else {
// 获取锁失败,说明正在处理
echo "请勿重复提交!";
}
fclose($fp);
方案2:Redis分布式锁 —— 适用于分布式集群、高并发
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lockKey = 'lock:order:' . $userId;
$token = uniqid('', true); // 唯一值,防止误删别人的锁
// 加锁:SET NX EX 10 (不存在才设置,10秒自动过期)
$locked = $redis->set($lockKey, $token, ['NX', 'EX' => 10]);
if ($locked) {
try {
// 执行业务逻辑
echo "业务处理中...";
// 延长锁时间(如果需要)
$redis->expire($lockKey, 10);
} finally {
// 释放锁:只有持有相同token才能删除
$script = <<<LUA
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
LUA;
$redis->eval($script, [$lockKey, $token], 1);
}
} else {
echo "操作太频繁,请稍后再试!";
}
说明:
SET NX EX是原子操作,避免传统SETNX+EXPIRE两个语句间的线程安全问题。
场景:多进程/多线程下的资源抢占(进程锁)
方案:flock + 共享文件,或 semaphore(信号量)
<?php
// 使用 System V 信号量(需要安装 pcntl、sysvsem 扩展)
$semId = sem_get(1234); // 获取一个信号量ID(不同进程间共享)
if (sem_acquire($semId)) { // 阻塞等待,直到获取锁
echo "进程 " . getmypid() . " 获得了锁\n";
sleep(2); // 模拟工作
sem_release($semId); // 释放锁
} else {
echo "获取锁失败\n";
}
场景:任务队列的抢占式消费(如多个worker抢任务)
方案:Redis BLPOP / RPOPLPUSH 或 RabbitMQ ACK机制
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
while (true) {
// BLPOP 阻塞直到有任务,同一个任务只会被一个worker取走
$task = $redis->blpop(['task_queue'], 5); // 超时5秒
if ($task) {
list($queue, $data) = $task;
echo "Worker " . getmypid() . " 抢到任务:" . $data . "\n";
// 处理任务...
sleep(1);
} else {
echo "等待任务...\n";
}
}
注意:真正的生产环境推荐使用 RabbitMQ(消费端设置 prefetch_count=1)或 Redis Stream(XREADGROUP 的消费者组机制)。
框架内置方案(推荐直接使用)
| 框架 | 抢占锁工具 |
|---|---|
| Laravel | Cache::lock('key', 10)->get() (基于Redis或文件) |
| Symfony | LockFactory 组件 |
| ThinkPHP | 可以使用 Redis 扩展,或 think\cache\driver\Redis 配合原子指令 |
Laravel示例:
$lock = Cache::lock('order_'.$userId, 10);
if ($lock->get()) {
// 业务...
$lock->release();
} else {
// 请勿重复提交
}
你应该用哪种?
- 简单防重复提交 →
flock(单机)或 RedisSET NX(分布式) - 任务队列抢占消费 → Redis
BLPOP或 RabbitMQ - 多进程资源互斥 → 文件锁或
sem_get
如果你要问的是更底层的 “PHP 如何实现类似操作系统的抢占式调度”,抱歉,PHP 本身不是抢占式多线程语言(它的多进程是协同式的,即使 pcntl 扩展也需要由程序员手动切换),所以无法实现真正意义上的“抢占”,但在业务层通过锁机制可以达到“互斥”效果,这就是实际工程中的“抢占”。