print()在后台卡住是因为stdout从行缓冲变为全缓冲,数据滞留在8KB缓冲区中;可通过-u参数、PYTHONUNBUFFERED=1或重置sys.stdout解决。

为什么print()在后台会卡住
因为Python默认对sys.stdout启用行缓冲(line buffering)——仅当遇到换行符或缓冲区满时才真正写入。但在后台运行(如nohup python script.py &或systemd服务)时,stdout不再是终端(tty),Python自动切换为**全缓冲(full buffering)**,缓冲区大小通常为8KB。若脚本只输出少量内容且不换行,数据就卡在内存里不动,看起来就像“僵死”。
如何验证是缓冲区导致的卡顿
最直接的办法:在脚本开头加import sys; sys.stdout.flush(),再手动调用一次print("test")后立刻sys.stdout.flush()。如果这时能立刻看到输出,基本锁定是缓冲问题。
- 更可靠的方式:用
strace -e write -p $(pgrep -f "script.py")观察是否有write()系统调用被阻塞或延迟 - 对比前台/后台行为:
python script.pyvspython script.py > /tmp/out.log 2>&1 &,看日志是否延迟落盘 - 注意:
logging模块默认也受stdout缓冲影响,除非显式设flush=True或用StreamHandler配buffering=1
三种可靠解法及适用场景
不要依赖print(..., flush=True)逐行加参数——容易漏、难维护。优先用启动参数或环境变量统一控制:
-
方案一(推荐):启动时加
-u参数 ——python -u script.py &。这会让stdin/stdout/stderr全部强制为无缓冲(unbuffered),兼容所有Python版本,且不影响代码逻辑 -
方案二:设置环境变量
PYTHONUNBUFFERED=1——PYTHONUNBUFFERED=1 python script.py &。效果同-u,适合集成进systemd或supervisord配置中 -
方案三:代码内重置
stdout——仅在必须兼容旧环境时用:import sys sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # Python 3.7+ # 或更兼容写法: import io sys.stdout = io.TextIOWrapper( sys.stdout.buffer, line_buffering=True, write_through=True )
容易被忽略的连锁反应
缓冲区问题常不是孤立的。它可能掩盖更深层的死锁,比如:
立即学习“Python免费学习笔记(深入)”;
- 子进程通过
subprocess.Popen启动后,父进程读proc.stdout但没设bufsize=1或没及时.readline(),导致子进程的stdout缓冲区填满后阻塞写入(尤其子进程也是Python) - 使用
logging.basicConfig但没传force=True,旧handler残留缓冲行为 - 容器环境中(如Docker),即使加了
-u,若stdout被重定向到/dev/null或管道,仍可能触发glibc层的额外缓冲,此时需配合stdbuf -oL(Linux)
真正稳定的后台脚本,得把缓冲控制、子进程回收、信号处理三者一起考虑——缓冲只是第一道门,跨过去才发现后面还有坑。


















