multiprocessing.Lock不能直接锁文件写入,因其仅保障共享内存互斥,对磁盘文件无效;并发write仍会导致内容错乱。

为什么 multiprocessing.Lock 不能直接锁文件写入
因为 multiprocessing.Lock 只保证进程间对共享内存变量的互斥访问,它对磁盘文件完全无效。多个进程同时调用 f.write() 或 print(..., file=f),即使加了 multiprocessing.Lock,仍可能因缓冲区、系统调用时机或文件指针偏移导致内容错乱、覆盖或截断。
用 os.open + os.O_APPEND 实现原子追加写入
Linux/macOS 下,以 os.O_APPEND 标志打开文件时,每次 os.write() 都会自动将文件指针移到末尾再写入,这个操作由内核保证原子性——这才是真正跨进程安全的“追加锁”。
关键点:
- 必须用
os.open()打开,不能用内置open()(后者不支持O_APPEND原子语义) - 写入必须用
os.write(fd, data.encode()),不能用file.write() - 需手动处理换行符:追加内容末尾要带
\n,否则所有日志会挤在一行 - Windows 不完全支持该原子性,生产环境需额外判断平台
示例:
import os
fd = os.open("log.txt", os.O_WRONLY | os.O_CREAT | os.O_APPEND)
os.write(fd, b"[INFO] task done\n")
os.close(fd)
跨平台方案:multiprocessing.Lock + 文件级重试
当必须使用 print() 或格式化写入(比如写 JSON 行),且需兼容 Windows 时,得靠进程锁 + 显式文件控制。但注意:multiprocessing.Lock 本身不能跨 fork 传递到子进程,必须在每个子进程中重新获取。
实操要点:
- 把
multiprocessing.Lock作为参数传给子进程函数,不能在全局定义后直接用 - 写入前加锁,写完立即释放,避免锁持有时间过长拖慢整体吞吐
- 建议用
with lock:语法,防止异常导致死锁 - 不要在锁内做耗时操作(如网络请求、复杂计算),只包住
f.write()和f.flush()
示例:
def worker(task_id, lock):
with lock:
with open("output.txt", "a") as f:
print(f"[{task_id}] processed", file=f)
f.flush() # 确保立即落盘,避免缓冲区延迟
更健壮的选择:用 logging 模块 + QueueHandler
标准库 logging 的 QueueHandler + QueueListener 组合,天然解决多进程日志写入竞争问题:所有子进程往队列发日志,仅主进程一个线程消费并写入文件。
优势与注意事项:
- 无需手动管理锁,无平台差异,自带线程安全
- 子进程必须用
logging.getLogger().handlers = []清空默认 handler,否则日志可能重复输出到 stderr - 队列满时默认阻塞,可通过
queue.put(..., block=False)改为丢弃,避免卡死 - 若需实时查看日志,记得在
FileHandler中设delay=False和mode="a"
这是生产环境最省心、也最容易被忽略的正解——很多人还在手写锁,其实 logging 已经替你做好了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











