You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

pyinstaller打包pywin32服务时multiprocessing子进程无法正常运行问题

问题表现
  • 基于pywin32开发的服务程序经PyInstaller打包后,前台运行时multiprocessing创建的子进程可正常执行日志写入、休眠逻辑,注册为Windows服务后子进程完全失效,事件日志中看不到子进程输出。
根因说明

问题由三个典型的Windows服务+Python多进程兼容坑导致:

  • multiprocessing.freeze_support()调用位置错误。Windows平台下multiprocessing默认使用spawn模式启动子进程,会重新拉起主exe程序,通过特殊命令行参数区分主/子进程流程。freeze_support()的作用是识别子进程启动参数,直接跳转执行子进程目标函数,你将这行代码放在服务类的实例方法main()中,子进程启动时根本无法执行到这行逻辑,会直接进入服务启动流程,因缺少服务控制管理器上下文直接崩溃。
  • Windows服务运行在Session 0隔离会话,子进程直接继承父进程的服务控制管理器句柄、日志句柄时,会因为会话权限隔离无法正常调用系统接口,轻则日志写入失败,重则子进程直接启动异常退出。
  • 原服务停止逻辑缺少跨进程同步事件,停止服务时无法通知子进程退出,容易出现服务卡住、子进程残留的问题。
修复步骤

1. 调整代码结构

首先将multiprocessing.freeze_support()移动到程序入口最顶层,在所有业务逻辑、服务初始化代码之前执行,同时显式指定Windows平台下多进程启动模式为spawn,避免默认逻辑兼容问题。
修复后的完整可运行代码如下:

import sys
import time
import traceback
import multiprocessing
import win32serviceutil
import win32service
import servicemanager

# !!!必须在所有代码最顶层调用,不能放到函数/类方法内部
multiprocessing.freeze_support()
# Windows环境强制指定spawn启动模式,规避服务环境兼容问题
if sys.platform == "win32":
    multiprocessing.set_start_method("spawn", force=True)


def log_and_sleep():
    # 子进程内独立调用日志接口,不依赖父进程继承的句柄
    try:
        servicemanager.LogInfoMsg("Child process started")
    except Exception:
        pass
    # 兜底用文件日志,避免Session 0权限导致事件日志写入失败
    # 注意路径选择服务账号有权限写入的目录,比如ProgramData下的专属目录
    log_path = "C:\\ProgramData\\TestService\\child.log"
    with open(log_path, "a", encoding="utf-8") as f:
        f.write(f"Child process running at {time.ctime()}\n")
    time.sleep(10)


def execute(stop_event):
    proc = multiprocessing.Process(target=log_and_sleep)
    proc.start()
    # 循环等待,支持服务停止时主动终止子进程
    while proc.is_alive():
        proc.join(timeout=1)
        if stop_event.is_set():
            proc.terminate()
            proc.join()
            break


class TestService(win32serviceutil.ServiceFramework):
    _svc_name_ = "Test_SERVICE"
    _svc_display_name_ = "Test_SERVICE"
    _svc_description_ = "Any Description"

    def __init__(self, args):
        super().__init__(args)
        # 新增停止事件,用于跨进程同步退出信号
        self.stop_event = multiprocessing.Event()

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        # 触发停止信号
        self.stop_event.set()
        self.ReportServiceStatus(win32service.SERVICE_STOPPED)
        servicemanager.LogMsg(
            servicemanager.EVENTLOG_INFORMATION_TYPE,
            servicemanager.PYS_SERVICE_STOPPED,
            (self._svc_name_, "Service stopped")
        )

    def SvcDoRun(self):
        servicemanager.LogMsg(
            servicemanager.EVENTLOG_INFORMATION_TYPE,
            servicemanager.PYS_SERVICE_STARTING,
            (self._svc_name_, "Service started")
        )
        try:
            execute(self.stop_event)
        except Exception:
            servicemanager.LogMsg(
                servicemanager.EVENTLOG_ERROR_TYPE,
                servicemanager.PYS_SERVICE_STOPPED,
                (self._svc_name_, traceback.format_exc())
            )


if __name__ == "__main__":
    if len(sys.argv) == 1:
        # 无命令行参数时由Windows服务管理器拉起
        if not hasattr(sys, "frozen"):
            print("Error: Must run as PyInstaller packaged executable when started by service manager\n")
            win32serviceutil.usage()
            sys.exit(1)
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(TestService)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        # 命令行模式下处理安装、启动、停止、卸载等指令
        win32serviceutil.HandleCommandLine(TestService)

2. 调整PyInstaller打包参数

打包时需要注意以下配置,避免依赖缺失导致运行失败:

  • 必须添加隐藏导入参数:--hidden-import=win32timezone,否则服务启动时会报缺少模块错误
  • 不要添加-w/--windowed无控制台参数,服务运行需要控制台上下文,使用默认控制台模式打包即可
  • 提前创建日志存放目录(比如示例中的C:\ProgramData\TestService),给服务运行账号授予写入权限,避免文件日志写入失败
  • 如果子进程用到了第三方依赖,按需添加--hidden-import参数确保依赖被正确打包

3. 服务安装与验证

  • 用管理员权限打开命令提示符,执行你的程序.exe install安装服务
  • 执行你的程序.exe start启动服务,或在服务管理器中手动启动
  • 验证点:
    • 服务状态保持“正在运行”,不会启动后立刻退出
    • Windows事件日志中可看到服务启动、子进程启动的相关日志
    • 配置的文件日志路径下能看到子进程写入的日志记录
    • 执行你的程序.exe stop时服务可正常停止,无残留子进程

内容的提问来源于stack exchange,提问作者li-la-lama

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 15:00:50