pyinstaller冻结打包多进程程序的bug:无限创建进程直至系统崩溃

前面写过两篇相关的文章:

  • PyQt应用程序打包
  • Python自动按键

这两篇文章都没有提到下面的这个重要问题:

采用Pyinstaller冻结打包多进程程序时,必须非常小心。这个技术线在Windows上会有一个非常严重的Bug。直接运行打包后的程序会造成无限创建进程,直到系统崩溃。

问题描述

本文针对一个非常具体的场景,需求包括以下要素:

  • 需要用PyQt5设计GUI程序
  • 需要调用其他库完成后台计算,计算与GUI线程松耦合
  • 通过PyInstaller打包成独立可执行程序

多进程的使用

在Python的标准库中,multiprocessing模块提供了多进程的支持。

如果我们采取这个技术线,则通常采用计算进程的方式来实现。

无限循环的计算进程

def worker(iq, oq):
    while True:
        # get input from input queue
        data = iq.get()
        # process data
        result = f(data) 
        # put result to output queue
        oq.put(result)

这个是一个典型的单纯的数据处理的计算过程,f(data)是一个计算函数,iq是输入队列,oq是输出队列。这个函数的内部是一个死循环,不断地从输入队列中获取数据,然后处理数据,最后将结果放到输出队列中。函数的大部分时间预计在get()函数的阻塞(当队列中没有数据时)和f(data)的计算上。

主进程启动计算进程

import multiprocessing

if __name__ == '__main__':


    input_q = multiprocessing.Queue()
    output_q = multiprocessing.Queue()

    multiprocessing.Process(target=worker, 
        args=(input_q, output_q), 
        daemon=True).start()    

这里的daemon=True表示这个进程是一个守护进程,当主进程结束时,这个进程也会结束;否则必须要等待这个进程结束后,才能结束主进程。如果不设置这里或者daemon=False,就需要在worker函数中设置一个退出条件。例如:

def worker(iq, oq):
    while True:
        # get input from input queue
        data = iq.get()
        if data == 'EXIT':
            break
        # process data
        result = f(data) 
        # put result to output queue
        oq.put(result)

这样,当主进程发送一个EXIT的数据时,计算进程就会退出。

数据共享

multiprocessing中,有两个机制可以实现进程中的数据共享:

  • Queue:进程间通信的队列
  • Pipe:进程间通信的管道

这两个机制中,Queue是比较高层次的,Pipe是比较底层的。所以后者的效率(也许)会更高。对于我们常规的应用,主要计算时间在上面的f(data),所以直接使用Queue就可以了。

PyInstaller和UPX

在交付Python应用时,通常的术语成为“冻结”(Frozen)。冻结的目的是将Python程序打包成一个独立的可执行文件,这个文件可以在没有Python解释器的环境中运行。

常见的冻结工具有:

  • PyInstaller
  • cx_Freeze

大概用得比较多的就是这两个,我使用前者更多一些。

这个工具的使用方法非常简单,只需要在命令行中输入:

pyinstaller your_script.py

这个命令会在当前目录下生成一个dist目录,里面包含了所有的依赖文件和可执行文件。

当然我们还可以设置一些选项,例如:

  • -D:生成一个目录,而不是一个单独的文件
  • -F:生成一个单独的文件

UPX压缩

当然,在生成可执行文件后,我们还可以使用UPX进行压缩。UPX是一个开源的可执行文件压缩工具,可以将可执行文件压缩到更小的体积。

  • UPX官网

通常在调用PyInstaller时,我们可以使用下面的命令:

pyinstaller -D your_script.py --upx-dir=upx-folder

这里,就设置了UPX的目录,当然,如果在当前的环境变量的PATH中有UPX,那么就不需要设置这个选项了。

可以用--no-upx来禁用UPX。

在常见(PyQt5)情况下,UPX还是能够提供超过50%的压缩率的。非常可观。

多进程与冻结的冲突

在Windows开发中,试图冻结一个上面的程序,不会有任何错误提示。

但是会带来一个非常严重的Bug。当客户运行冻结的exe时,程序会疯狂创建新的进程,直到系统崩溃。

请不要测试……必须直接关机。

这个问题的原因是,multiprocessing模块在Windows中使用spawn方法来创建新的进程。而在冻结程序中,没有python解释器,所以multiprocessing模块会调用我们冻结得到的exe,然后这个exe又会调用multiprocessing模块,然后……就会无限循环。

解决方案

在冻结多进程multiprocessing的程序时,我们需要在if __name__ == '__main__':中调用freeze_support()函数。

if __name__ == '__main__':
    multiprocessing.freeze_support()
    input_q = multiprocessing.Queue()
    output_q = multiprocessing.Queue()

    multiprocessing.Process(target=worker, 
        args=(input_q, output_q), 
        daemon=True).start()    

这个调用必须在任何其他的multiprocessing调用之前进行。这里就在if __name__ == '__main__':中调用。

一个表现良好的例子

界面

下面是一个表现良好的例子.

这个例子是一个简单的加法计算器,用户输入两个数,然后计算它们的和。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

后台的计算进程是一个无限循环的进程,不断地从输入队列中获取数据,然后计算,最后将结果放到输出队列中。这里的PyQt5部分采取了硬核布局(!)。

代码

import multiprocessing
import sys

from PyQt5.QtWidgets import QApplication, QLineEdit, QListWidget, QPushButton
from PyQt5.QtWidgets import QMainWindow


# process to calculate, data by Queue across processes
def calculate(input_queue_a: multiprocessing.Queue, output_queue_a: multiprocessing.Queue):
    while True:
        try:
            x, y = input_queue_a.get()
            result = x + y
            output_queue_a.put({"x": x, "y": y, "result": result})
        except:
            continue

def tryParse(s, default_value=0.0):
    try:
        num = float(s)
    except ValueError:
        num = default_value
    return num


# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    multiprocessing.freeze_support()

    input_queue = multiprocessing.Queue()
    output_queue = multiprocessing.Queue()

    multiprocessing.Process(target=calculate, daemon=True, args=(input_queue, output_queue)).start()

    # PyQt Window
    app = QApplication(sys.argv)
    window = QMainWindow()
    window.setWindowTitle('Multiprocessing')

    # add ui elements here
    num1_input = QLineEdit(window)
    num1_input.move(20, 20)
    num1_input.resize(200, 30)

    num2_input = QLineEdit(window)
    num2_input.move(20, 60)
    num2_input.resize(200, 30)

    calculate_button = QPushButton('Calculate', window)
    calculate_button.move(20, 100)
    calculate_button.resize(200, 30)

    output_list = QListWidget(window)
    output_list.move(20, 140)
    output_list.resize(200, 200)


    def calculate():
        x = tryParse(num1_input.text() or 0.0)
        y = tryParse(num2_input.text() or 0.0)
        input_queue.put((x, y))
        try:
            xy_result = output_queue.get()
            output_list.addItem(f"{xy_result['x']} + {xy_result['y']} = {xy_result['result']}")
        except Exception as e:
            output_list.addItem(f"Error{e}")

        output_list.scrollToBottom()


    calculate_button.clicked.connect(calculate)
    num2_input.returnPressed.connect(calculate)
    num1_input.returnPressed.connect(calculate)

    window.resize(240, 360)
    # fix window size, set it to non-resizable
    window.setFixedSize(240, 360)

    window.show()

    # exit app when close window
    sys.exit(app.exec_())

其他注意事项

程序充分考虑了用户输入的错误,当用户输入的不是数字时,会自动转换为0.0。当用户输入的是空字符串时,也会转换为0.0。

并且,按钮,输入框的回调函数都是一个函数。

值得注意的是,Python的multiprocessing.Queue是什么都能放,简直是头发安全的程序设计。这里,我们传出的数据是一个字典,包含了输入的两个数和计算的结果。通过重复输入的数据,我们更大程序的避免了计算进行和主进程的耦合。这又是一个典型面向头发安全的编程习惯。

打包

在打包之前,应该用pip安装pyinstaller。这个时候,我们可以使用下面的命令:

# 设置UPX目录
pyinstaller -D -w ./main.py -n addUpx -y --upx-dir=D:/Users/User/upx-4.2.4-win64

# 不使用UPX
pyinstaller -D -w ./main.py -n addWithoutUpx --noupx -y

在我们注意了所有的事项后,我们就可以放心地交付我们的程序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948605.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GRAPE——RLAIF微调VLA模型:通过偏好对齐提升机器人策略的泛化能力(含24年具身模型汇总)

前言 24年具身前沿模型大汇总 过去的这两年,工作之余,我狂写大模型与具身的文章,加之具身大火,每周都有各种朋友通过CSDN私我及我司「七月在线」寻求帮助/指导(当然,也欢迎各大开发团队与我司合作共同交付&#xff09…

家教老师预约平台小程序系统开发方案

家教老师预约平台小程序系统将连接学生/家长与家教老师,提供一站式的家教服务预约体验。 一、用户需求分析1、家教老师:希望获得更多的学生资源,通过平台展示自己的教学特长和经验,管理个人日程,接收并确认预约请求&a…

【Linux】:多线程(读写锁 自旋锁)

✨ 倘若南方知我意,莫将晚霞落黄昏 🌏 📃个人主页:island1314 🔥个人专栏:Linux—登神长阶 ⛺️ 欢迎关注:👍点赞 &#…

unity团结云下载项目

今天开plastic scm发现它云服务好像停了哈,在hub里下载云端项目也不会出现在项目列表里,之前也有发邮件说让提前迁移到团结云。打开云仓库会弹这个,大概就是plastic scm无法解析域名地址吧 研究了一下团结云咋使,官方手册看半天也…

C语言string函数库补充之strstr

这次讲解一个函数strstr 它的功能是在一个字符串(称为“主字符串”)中查找另一个字符串(称为“子字符串”)的第一个出现位置。如果找到了子字符串,strstr 函数会返回一个指向子字符串在主字符串中首次出现位置的指针&…

2025-01-04 Unity插件 YodaSheet2 —— 基础用法

文章目录 环境配置1 创建 YadeSheetData2 读取方式2.1 表格读取2.2 列表读取 3 自定义设置3.1 修改代码生成位置3.2 添加列表支持3.2.1 修改 DataTypeMapper.cs3.2.2 修改 SheetDataExtensions.cs3.2.3 修改 CodeGeneratorEditor.cs3.2.4 测试 ​ 官方文档: Unity …

探索 JMeter While Controller:循环测试的奇妙世界

嘿,宝子们!今天咱们就来聊聊 JMeter 里超级厉害的 While 控制器,它就像是一把神奇的钥匙,能帮我们打开循环测试的大门,模拟出各种各样复杂又有趣的场景哦! 一、While 控制器初印象 想象一下,你…

Linux中隐藏操作身法

从历史记录中删除指定的命令 假设历史记录中已经包含了一些你不希望记录的命令。这种情况下我们怎么办?很简单。通过下面的命令来删除: history | grep "keyword"例如:history | grep set o history 批量第二条和第四条删除: sed…

单片机--51- RAM

1.概览某个51单片机对空间区域的划分: 2.RAM被分配的区域是256bytes, 通常8051单片机ram是128bytes 8052的ram是256bytes(其中高128位的地址和sfr区域地址重合,物理区域不同) extern uint32_t alarm_cnt_1; uint32…

使用GitLab+Jenkins搭建CICD执行环境

使用GitLabJenkins搭建CI\CD执行环境 前言什么是DevOps?什么是CI/CD?使用GitLabJenkins搭建CI\CD执行环境GitLab安装1. 安装和配置所需的依赖2. 下载并安装极狐GitLab3. 登录极狐GitLab 实例4.常用gitlab指令5.修改密码 Jenkins安装1.Jenkins 的主要特点…

仿生的群体智能算法总结之一(十种)

群体智能算法是一类通过模拟自然界中的群体行为来解决复杂优化问题的方法。以下是10种常见的群体智能算法: 编号 算法名称(英文) 算法名称(中文) 年份 作者 1 Ant Colony Optimization (ACO) 蚁群优化算法 1991 Marco Dorigo 2 Particle Swarm Optimization (PSO) 粒子群优…

【管道——二分+区间合并】

题目 思路 区间合并 1、按照左端点排序2、遍历窗口,若窗口非法,继续遍历;否则执行33、若是第一个窗口,设定合并结果初值,判断结果左端点是否造成“起点过大”,是,FALSE退出;否则执行…

《Rust权威指南》学习笔记(三)

泛型和trait 1.泛型可以提高代码的复用能力,泛型是具体类型或其他属性的抽象代替,可以看成是一种模版,一个占位符,编译器在编译时会将这些占位符替换成具体的类型,这个过程叫做“单态化”,所以使用泛型的…

Unity 中计算射线和平面相交距离的原理

有此方法 能够计算射线和平面是否相交以及射线起点到平面交点的距离 代码分析 var dot Vector3.Dot(ray.direction, plane.normal);计算射线和平面法线的点积,如果大于等于0,则说明射线和平面没有相交,否则,说明射线和平面相交…

Docker安装Prometheus和Grafana

概念简述 安装prometheus 第一步:确保安装有docker 第二步:拉取镜像 第三步:准备相关挂载目录及文件 第四步:启动容器 第五步:访问测试 安装grafana 第一步:确保安装有docker 第二步:拉…

《Rust权威指南》学习笔记(一)

基本介绍 1.Rust使用场景 :需要运行速度、需要内存安全、更好的利用多处理器。程序员无法在安全的Rust代码中执行任何非法的内存操作。相对于C#等带有垃圾回收机制的语言来讲,Rust遵循了零开销抽象(Zero-Cost Abstraction)规则&a…

才气小波与第一性原理

才气小波与第一性原理 才气小波与第一性原理具身智能云藏山鹰类型物热力学第二定律的动力机械外骨骼诠释才气小波导引社会科学概论软凝聚态数学意气实体过程王阳明代数Wangyangmingian王阳明算符才气语料库命运社会科学概论意气实体过程业务分层框架示例 才气小波与第一性原理 …

JAVA:利用 Redis 实现每周热评的技术指南

1、简述 在现代应用中,尤其是社交媒体和内容平台,展示热门评论是常见的功能。我们可以通过 Redis 的高性能和丰富的数据结构,轻松实现每周热评功能。本文将详细介绍如何利用 Redis 实现每周热评,并列出完整的实现代码。 2、需求分…

Fabric环境部署-安装Go

安装go语言环境 国内镜像:Go下载 - Go语言中文网 - Golang中文社区 1.选择版本下载后解压:注意go1.11.linux-amd64.tar.gz换成你下的 sudo tar zxvf go1.21.linux-amd64.tar.gz -C /usr/local 2.. 创建Go目录 mkdir $HOME/go 3. 用vi打开~./bashrc&…

计算队列中的‘捣乱分子’对数:一种量化无序程度的方法

计算队列中的‘捣乱分子’对数:一种量化无序程度的方法 前言解题思路关键点实现代码时间复杂度分析前言 在日常生活中,我们经常会遇到需要排队的场景,比如买票、候车、就餐等。在理想的排队情况下,人们会按照某种顺序(如先到先服务)整齐地排成一列。然而,总有一些人不遵…