使用ThreadPoolExecutor多线程写文件出现输出不完整问题
问题原因分析
核心问题出在Windows系统与Unix-like系统(如Mac)对文件追加写入的原子性实现差异:
- 在Windows下,Python以
'a'模式打开文件时,"获取文件末尾位置+执行写入"是两个分离的操作,并非原子性的。当多个线程并发执行时,线程A刚获取到文件末尾位置,线程B就完成了写入并改变了文件长度,此时线程A再写入会直接覆盖线程B的内容,导致字符丢失。 - 而Mac等Unix-like系统中,追加模式的写入是原子操作,系统会自动保证每个写入请求都追加到文件末尾,不会出现覆盖。
max_workers=3时问题更频繁,是因为线程数增加后,并发写入的冲突概率直线上升。
可行解决方案
1. 线程锁保证写入原子性
这是最直接的修复方式,通过threading.Lock确保同一时间只有一个线程操作文件,彻底避免并发冲突。
修改后的代码:
from concurrent import futures import threading write_lock = threading.Lock() def print_one(character): with write_lock: with open('test.txt', 'a') as output: output.write(character + ' ') def print_many(): with futures.ThreadPoolExecutor(3) as executor: executor.map(print_one, ['1','2','3','4','5','6','7','8','9','10']) if __name__=='__main__': with open('test.txt', 'w') as init: pass print_many()
优缺点:代码改动极小,完全解决问题;高并发场景下有轻微性能损耗,但对你的场景几乎可忽略。
2. 单线程统一处理写入
用队列解耦任务执行与文件写入:多线程只负责生成要写入的内容,将内容放入线程安全队列;单独一个线程从队列取内容写入文件。既保留多线程并行能力,又避免写入冲突。
修改后的代码:
from concurrent import futures import queue import threading def print_one(character, q): q.put(character + ' ') def writer(q): with open('test.txt', 'a') as output: while True: content = q.get() if content is None: break output.write(content) q.task_done() def print_many(): q = queue.Queue() threading.Thread(target=writer, args=(q,), daemon=True).start() with futures.ThreadPoolExecutor(3) as executor: executor.map(print_one, ['1','2','3','4','5','6','7','8','9','10'], [q]*10) q.join() q.put(None) if __name__=='__main__': with open('test.txt', 'w') as init: pass print_many()
优缺点:性能优异,适合后续任务逻辑复杂的场景;代码稍复杂,但解耦清晰,扩展性强。
3. 全局打开文件+锁(优化IO开销)
如果不想频繁打开关闭文件,可以全局打开文件,但必须配合锁保证写入的线程安全(Python文件对象的write方法本身并非线程安全)。
修改后的代码:
from concurrent import futures import threading write_lock = threading.Lock() def print_one(character, output_file): with write_lock: output_file.write(character + ' ') output_file.flush() def print_many(): with open('test.txt', 'a') as output: with futures.ThreadPoolExecutor(3) as executor: executor.map(print_one, ['1','2','3','4','5','6','7','8','9','10'], [output]*10) if __name__=='__main__': with open('test.txt', 'w') as init: pass print_many()
优缺点:减少文件IO开销,性能较好;加锁后既不会丢失字符,也能保留多线程的随机写入顺序,解决你之前遇到的“顺序接近1到10”的问题。
内容的提问来源于stack exchange,提问作者hzh
相关产品推荐
相关产品推荐

