PHP 怎么PHP 抢占

wen PHP项目 1

本文目录导读:

PHP 怎么PHP 抢占

  1. 场景:防止同一个用户短时间内重复提交订单(防刷)
  2. 场景:多进程/多线程下的资源抢占(进程锁)
  3. 场景:任务队列的抢占式消费(如多个worker抢任务)
  4. 框架内置方案(推荐直接使用)
  5. 总结:你应该用哪种?

在 PHP 中,“抢占” 通常指以下几种不同的场景,你需要根据具体需求选择对应方案:

  1. 防止并发请求冲突(锁机制)
  2. 多进程/多线程下的资源竞争(进程锁、共享内存)
  3. 任务队列的公平调度(消费者抢占)

以下针对最常见的 “防止并发请求导致数据异常” 场景(即“独占锁”)给出具体实现。


场景:防止同一个用户短时间内重复提交订单(防刷)

核心思路:使用文件锁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 StreamXREADGROUP 的消费者组机制)。


框架内置方案(推荐直接使用)

框架 抢占锁工具
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(单机)或 Redis SET NX(分布式)
  • 任务队列抢占消费 → Redis BLPOP 或 RabbitMQ
  • 多进程资源互斥 → 文件锁或 sem_get

如果你要问的是更底层的 “PHP 如何实现类似操作系统的抢占式调度”,抱歉,PHP 本身不是抢占式多线程语言(它的多进程是协同式的,即使 pcntl 扩展也需要由程序员手动切换),所以无法实现真正意义上的“抢占”,但在业务层通过锁机制可以达到“互斥”效果,这就是实际工程中的“抢占”。

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