多进程导致文件日志异常,为何一个Handler会干扰另一个?
问题描述
多进程会导致文件日志出现异常:
- 已写入的日志行可能被删除
- 新日志行可能无法写入
- 日志行顺序可能错乱
不使用多进程时,日志功能正常。
我了解可以使用QueueHandler解决,但想明白为何向某个Handler写入日志时,会干扰另一个Handler的正常工作。
复现代码
设置USE_MP=False或取消exit()的注释,可验证不执行多进程代码时日志功能正常。
import logging import multiprocessing import time from multiprocessing import Pool from typing import Dict, List USE_MP = True logger = logging.getLogger() logger.addHandler(logging.FileHandler(filename="test.log", mode="w")) logger.setLevel(logging.DEBUG) mplogger = multiprocessing.log_to_stderr() mplogger.addHandler(logging.StreamHandler()) mplogger.setLevel(logging.DEBUG) def time_consuming_function(file_name): logger.info(f"Running time_consuming_function with {file_name}") time.sleep(1) return file_name, file_name def mp(file_names: List[str]) -> Dict[str, str]: logger.info(f"Running mp...") with Pool() as p: return { file_name: file_name for file_name, file_name in p.imap_unordered( time_consuming_function, file_names ) } def non_mp(file_names: List[str]) -> Dict[str, str]: logger.info(f"Running non-mp...") return { file_name: file_name for file_name, file_name in map(time_consuming_function, file_names) } def main(): logger.info("Start run...") file_names = list("ABCDE") # exit() if USE_MP: mp(file_names) else: non_mp(file_names) logger.info("End run.") if __name__ == "__main__": main()
Python版本:3.7
问题解答
这得从Python多进程的底层机制说起:
- 当你用
Pool创建子进程时,系统会通过fork(Unix)或spawn(Windows)生成新进程。子进程会继承父进程的文件描述符,但每个进程的文件指针位置是独立维护的。 - 你的代码里,父进程启动时就用
FileHandler打开了test.log(mode="w"),此时文件被清空,文件指针停在开头。子进程创建后,各自拿着这个文件的描述符,但它们的指针初始位置都是开头——父进程写入日志后指针已经移到后面,子进程写入时就会从开头覆盖父进程的内容,这就是日志行被“删除”的原因。 - 多个子进程同时写入时,没有任何同步机制,各自的写入操作会互相穿插,导致日志顺序错乱;甚至会因为文件指针的冲突,出现部分内容写不进去的情况。
你觉得是“一个Handler干扰另一个”,其实本质是同一个文件被多个进程的Handler同时操作,每个Handler都独立管理自己的文件状态,完全没有协调,自然会出各种问题。
QueueHandler之所以能解决,是因为它把所有进程的日志事件都扔进一个队列,交给单独的一个线程/进程统一写入文件,保证了文件操作的原子性和顺序性,从根源上避免了多进程直接操作文件的冲突。
内容的提问来源于stack exchange,提问作者3UqU57GnaX
相关产品推荐
相关产品推荐

