本文目录导读:

在 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 扩展也需要由程序员手动切换),所以无法实现真正意义上的“抢占”,但在业务层通过锁机制可以达到“互斥”效果,这就是实际工程中的“抢占”。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播