
本文介绍使用 `portalocker` 库实现跨进程文件写入锁,确保多实例 python 脚本在 windows 下安全、有序地向同一文本文件追加内容,避免竞态与覆盖,且支持自动等待而非报错退出。
在 Windows 环境下运行多个 Python 进程(如并行执行相同脚本)时,若它们都尝试写入同一个文本文件(如记录已完成任务的 completed.txt),极易因缺乏同步机制导致数据丢失、内容错乱或 PermissionError/IOError 异常。你当前的逻辑是:每处理完一个任务就将该任务名追加到 completedlist,再一次性写入文件——这种“累积后批量写入”模式虽简洁,但在多实例场景下存在明显风险:两个进程可能同时打开文件、覆盖彼此内容,或因写入顺序混乱导致重复/遗漏。
解决此问题的核心是强制写入操作互斥,即任一进程写入时,其他进程必须等待。Python 标准库中的 threading.Lock 仅作用于线程内,无法跨进程;而 Windows 对文件句柄的独占控制又较严格,简单用 open(..., 'a') 并不能保证原子性。此时,推荐使用轻量、稳定、跨平台的第三方库 portalocker ——它通过调用系统级文件锁(Windows 使用 _winapi.LockFileEx,类 Unix 使用 fcntl.flock)实现真正的进程级排他访问。
✅ 正确做法:带自动等待的排他写入
首先安装依赖:
pip install portalocker
然后重构你的写入逻辑(注意:必须使用 'a' 模式打开文件,并在写入前加锁、写入后解锁):
立即学习“Python免费学习笔记(深入)”;
import portalocker
import time
FILE_PATH = "completed.txt"
def append_to_file(items):
"""安全地将 items 列表逐行追加到文件,自动等待锁释放"""
for item in items:
# 关键:每次写入单行,避免长时持锁影响并发效率
while True:
try:
with open(FILE_PATH, 'a', encoding='utf-8') as f:
portalocker.lock(f, portalocker.LOCK_EX | portalocker.LOCK_NB)
f.write(f"{item}\n")
f.flush() # 确保立即写入磁盘,避免缓冲区延迟
portalocker.unlock(f)
break # 写入成功,跳出重试循环
except portalocker.LockException:
# 文件被其他进程锁定,等待 0.5 秒后重试
time.sleep(0.5)
except OSError as e:
# 兜底:捕获可能的权限/路径异常(如文件被删除)
print(f"写入失败: {e}")
time.sleep(1)
# 在你的主循环中调用(保持原有结构不变)
for i in newlist:
runFunction()
completedlist.append(i)
append_to_file([i]) # 每次只追加当前项,更健壮⚠️ 注意事项与最佳实践
- 不要批量写整个 completedlist:原逻辑中“累积后全量写入”会显著延长锁持有时间,降低并发吞吐。改为每完成一项立即追加一行,既保持语义清晰,又最小化锁竞争窗口。
- 务必使用 'a'(append)模式:确保所有写入都发生在文件末尾,避免因文件指针偏移引发覆盖。
- 显式调用 f.flush():防止操作系统缓存导致内容未及时落盘,尤其在多进程环境下至关重要。
- LOCK_NB + time.sleep() 实现非阻塞等待:避免 portalocker.lock(f, portalocker.LOCK_EX) 直接抛出异常终止程序;通过捕获 LockException 并轮询,实现优雅等待。
- 编码声明不可省略:Windows 默认编码易引发 UnicodeEncodeError,显式指定 encoding='utf-8' 提升鲁棒性。
- 避免锁文件本身被误删:确保 completed.txt 不被其他程序意外删除或移动,否则锁机制失效。
通过以上改造,你无需更改脚本整体流程,即可安全启动任意数量的实例并行处理 newlist ——每个任务结果都会按实际完成顺序、无冲突地持久化到同一文件中。










