You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何PHP代码中首次执行(1607061065)阻塞,二次执行不阻塞?

问题:PHP文件锁执行阻塞疑问

场景与代码

我通过ab工具发送并发请求执行以下PHP脚本:

<?php
$fileName = "file.txt";

$fp = fopen($fileName, "a");
if (flock($fp, LOCK_EX | LOCK_NB)) {
    $runId = rand();
    fwrite(
        $fp,
        "processing task [runId]: $runId; [pid]: ". getmypid() ." started 
" .
        (new DateTime())->format(DateTime::ATOM) . " 

"
    );

    sleep(3);
    flock($fp, LOCK_UN);
    fwrite(
        $fp,
        "processing task [runId]: $runId; [pid]: ". getmypid() ." finished 
" .
        (new DateTime())->format(DateTime::ATOM) . " 

"
    );
    fclose($fp);
    exit;
}
fwrite(
    $fp,
    "script is busy [pid]: ". getmypid() ." 
" .
    (new DateTime())->format(DateTime::ATOM) . " 

"
);

执行命令为:

ab -c 3 -n 8 http://localhost/

(即并行运行3个实例,共执行8次)

执行结果

执行完成后file.txt中的内容如下:

processing task [runId]: 1607061065; [pid]: 2507 started
2022-12-22T17:03:22+00:00

processing task [runId]: 1607061065; [pid]: 2507 finished
2022-12-22T17:03:25+00:00

processing task [runId]: 1664361639; [pid]: 2503 started
2022-12-22T17:03:25+00:00

script is busy [pid]: 2507
2022-12-22T17:03:25+00:00

script is busy [pid]: 2502
2022-12-22T17:03:25+00:00

script is busy [pid]: 2502
2022-12-22T17:03:25+00:00

script is busy [pid]: 2507
2022-12-22T17:03:25+00:00

script is busy [pid]: 2502
2022-12-22T17:03:25+00:00

script is busy [pid]: 2507
2022-12-22T17:03:25+00:00

processing task [runId]: 1664361639; [pid]: 2503 finished
2022-12-22T17:03:28+00:00

疑问:为什么runId为1607061065的进程首次执行时会阻塞其他请求,而runId为1664361639的进程执行时却没有阻塞其他请求?


解答

核心原因:锁状态变化与进程调度时机的匹配

  1. 首次执行的阻塞逻辑
    当runId=1607061065的进程(pid=2507)获取LOCK_EX排他锁后,其他并发启动的进程(pid=2503、2502)调用flock($fp, LOCK_EX | LOCK_NB)会直接返回失败——因为排他锁会阻止任何其他进程获取锁,加上LOCK_NB非阻塞标志,进程不会等待锁释放,直接进入"script is busy"分支。这就是你看到的首次执行时其他请求全部被"阻塞"(实际是直接失败)的原因。

  2. 第二次执行无阻塞的真相
    runId=1664361639的进程(pid=2503)能获取锁,是因为pid=2507的进程在sleep(3)后调用flock($fp, LOCK_UN)释放了锁。此时:

  • 所有"script is busy"的记录时间戳都和pid=2507完成任务、pid=2503开始任务的时间完全一致,说明这些进程是在锁释放的同一时间点完成了锁尝试——它们在pid=2507持有锁期间就已经发起了锁请求并返回失败,只是输出操作刚好和锁释放、pid=2503获取锁的时间重合。
  • pid=2503是在锁释放的瞬间抢到了锁,而其他进程因为已经执行过锁判断流程,不会再次尝试获取锁,所以直接输出忙碌信息,不会被新的锁持有状态阻塞。
  1. 补充说明
    代码存在一个小隐患:解锁后直接写入文件,此时文件没有锁保护,不过这不是本次问题的核心。本质上两次执行的差异是并发进程的调度时机——第一次所有请求都在锁持有期发起锁请求,全部失败;第二次是锁释放后,刚好有一个进程抢到锁,其他进程已经完成锁判断,不会被新锁阻塞。

内容的提问来源于stack exchange,提问作者sprutex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 00:10:20