flock()是唯一可靠、轻量、无需额外依赖的单机文件并发控制手段,必须配合fopen()使用,锁绑定于文件指针而非路径;多进程同时fopen('log.txt','a')后fwrite会因共享文件偏移导致写入错乱、覆盖或截断,这是posix i/o固有行为,非php缺陷。

flock() 是唯一可靠、轻量、无需额外依赖的单机文件并发控制手段,但必须和 fopen() 配合使用,且锁绑定在文件指针上,不是路径上。
为什么直接 fopen + fwrite 会出错
多个进程同时 fopen('log.txt', 'a') 后调用 fwrite(),底层系统可能让它们共享同一个文件偏移量,导致写入位置错乱、内容覆盖或截断。这不是 PHP 的 bug,而是 POSIX 文件 I/O 模型的固有行为。常见现象包括:日志行被撕裂(半行写入)、计数器跳变、JSON 文件变成非法格式。
flock() 必须配合 fopen 才能生效
flock() 不接受文件路径,只操作已打开的文件指针。这意味着:
- 不能对
file_get_contents()或file_put_contents()的结果加锁——它们不返回可锁定的句柄 - 必须用
fopen()打开文件,且模式要匹配锁类型:LOCK_EX要求可写(如'c'、'a'、'r+'),LOCK_SH可用于只读打开('r') - 锁随文件指针存在,
fclose()会自动释放,但建议显式调用flock($fp, LOCK_UN),避免因异常提前退出导致锁残留
阻塞 vs 非阻塞:怎么选 LOCK_NB
默认 flock($fp, LOCK_EX) 会一直等待,直到锁可用——这在 Web 请求中容易拖垮响应时间。生产环境应优先用非阻塞模式:
- 加锁失败时立即返回
false,可快速降级(如写入临时队列、记录告警、返回 503) - 若需重试,用
usleep(rand(1000, 50000))避免自旋竞争,最多重试 3–5 次 -
LOCK_EX | LOCK_NB组合是安全写入的起点,不要省略LOCK_NB
别踩这些坑
实际调试中最常翻车的点:
- 对只读打开的文件句柄(
fopen('data.json', 'r'))调用flock($fp, LOCK_EX),必定返回false,但很多人没检查返回值就直接fwrite() - 在
flock()成功后、fwrite()前发生 fatal error(如内存溢出),锁不会被释放——所以临界区逻辑越短越好,避免复杂计算或远程调用 - Windows 下某些 NFS 共享目录不支持
flock(),测试时务必在目标部署环境验证 - 不要把锁文件和业务文件混用:专用锁文件(如
/tmp/myapp.lock)比锁日志文件本身更可控
最易被忽略的是锁的“建议性”本质:它不强制阻止其他进程写入,只依赖所有参与者都主动调用 flock()。只要有一个脚本绕过它,整个机制就失效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











