为何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的进程执行时却没有阻塞其他请求?
解答
核心原因:锁状态变化与进程调度时机的匹配
首次执行的阻塞逻辑
当runId=1607061065的进程(pid=2507)获取LOCK_EX排他锁后,其他并发启动的进程(pid=2503、2502)调用flock($fp, LOCK_EX | LOCK_NB)会直接返回失败——因为排他锁会阻止任何其他进程获取锁,加上LOCK_NB非阻塞标志,进程不会等待锁释放,直接进入"script is busy"分支。这就是你看到的首次执行时其他请求全部被"阻塞"(实际是直接失败)的原因。第二次执行无阻塞的真相
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是在锁释放的瞬间抢到了锁,而其他进程因为已经执行过锁判断流程,不会再次尝试获取锁,所以直接输出忙碌信息,不会被新的锁持有状态阻塞。
- 补充说明
代码存在一个小隐患:解锁后直接写入文件,此时文件没有锁保护,不过这不是本次问题的核心。本质上两次执行的差异是并发进程的调度时机——第一次所有请求都在锁持有期发起锁请求,全部失败;第二次是锁释放后,刚好有一个进程抢到锁,其他进程已经完成锁判断,不会被新锁阻塞。
内容的提问来源于stack exchange,提问作者sprutex
相关产品推荐
相关产品推荐

