ProcessPoolExecutor在Windows函数内无法输出日志,Unix/Mac正常
解决Windows下Python多进程日志不显示的问题
这个问题我太熟悉了!Windows和Unix/Linux/macOS的多进程实现机制差异确实是罪魁祸首——Windows用的是spawn方式创建子进程,而Unix系用的是fork。
fork会直接复制父进程的整个内存空间,包括已经配置好的logger实例和所有handler,所以子进程能直接继承父进程的日志配置。- 但
spawn是从头启动一个全新的Python解释器,重新导入所有模块,父进程里的logger配置完全不会被带过去,这就是你在Windows看不到log_pid函数日志的核心原因。
两种可行的修改方案
方案1:在任务函数中重新初始化日志
把日志配置逻辑封装成一个单独的函数,让每个子进程执行任务时先调用这个函数初始化日志:
import logging import sys from concurrent.futures import ProcessPoolExecutor import os def setup_logging(): # 获取全局logger实例 logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) # 关键:避免重复添加handler(如果多次调用该函数) if not logger.handlers: handler = logging.StreamHandler(sys.stdout) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) def log_pid(x): # 子进程启动后先初始化日志 setup_logging() logger = logging.getLogger(__name__) logger.info(f'Executing on process: {os.getpid()} with argument {x}') return os.getpid() if __name__ == '__main__': # 父进程也先配置好日志 setup_logging() logger = logging.getLogger(__name__) logger.info(f'Parent process ID: {os.getpid()}') with ProcessPoolExecutor(max_workers=2) as executor: results = executor.map(log_pid, [1,2,3,4]) for res in results: logger.info(f'Received result from process: {res}')
方案2:用ProcessPoolExecutor的initializer统一初始化
ProcessPoolExecutor提供了initializer参数,可以指定一个初始化函数,每个子进程启动时都会自动执行它,这样不用在每个任务函数里重复写初始化代码:
import logging import sys from concurrent.futures import ProcessPoolExecutor import os def setup_logging(): logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) if not logger.handlers: handler = logging.StreamHandler(sys.stdout) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) def log_pid(x): logger = logging.getLogger(__name__) logger.info(f'Executing on process: {os.getpid()} with argument {x}') return os.getpid() if __name__ == '__main__': setup_logging() logger = logging.getLogger(__name__) logger.info(f'Parent process ID: {os.getpid()}') # 传入initializer,让所有子进程启动时自动执行日志配置 with ProcessPoolExecutor(max_workers=2, initializer=setup_logging) as executor: results = executor.map(log_pid, [1,2,3,4]) for res in results: logger.info(f'Received result from process: {res}')
关键注意事项
- 必须加
if __name__ == '__main__'::Windows的spawn机制会重新导入主模块,这个判断能避免子进程重复执行主程序的逻辑(比如再次创建进程池)。 - 检查handler是否存在:如果不判断
if not logger.handlers,子进程多次初始化日志时会重复添加handler,导致同一条日志被输出多次。
内容的提问来源于stack exchange,提问作者blahblahblah
相关产品推荐
相关产品推荐

