skywind3000 / PyStand

:rocket: Python Standalone Deploy Environment !!
MIT License
773 stars 86 forks source link

有多进程调用时,会重复启动Qt主窗口直到内存耗尽 #21

Closed rockswang closed 2 years ago

rockswang commented 2 years ago

入口代码如下,开头会先启动一个后台进程。 python 直接执行正常,通过PyStand.exe 启动,就会不断启动Qt窗口,直到内存耗尽为止。 PyInstaller也有类似问题,按照PyInstaller的方案,加上了freeze_support(),但是问题依旧。 是否需要在PyStand.exe的主程序里添加此调用?

if "__main__" == __name__:
    multiprocessing.freeze_support()
    multiprocessing.Process(target=VideoServer.start_server, daemon=True).start()

    logger.info("启动程序")
    app = QApplication(sys.argv)
    app.setStyleSheet(BASE_STYLE_SHEET)
    win = AppWin(sys.argv)
    win.show()
    sys.exit(app.exec())
myd7349 commented 2 years ago

可以尝试一下在调用 multiprocessing.freeze_support() 前设置一下 sys.frozen

if not hasattr(sys, 'frozen'):
    sys.frozen = True

multiprocessing.freeze_support()

PyInstaller 的 bootloader 里会帮你设置这个值,因此你只需要调用 freeze_support 就可以了:

https://github.com/pyinstaller/pyinstaller/blob/3ea48fd875e786ecbb3c160209791aa11d55ae99/PyInstaller/loader/pyiboot01_bootstrap.py#L27-L29

https://pyinstaller.org/en/v3.3.1/runtime-information.html

The PyInstaller bootloader adds the name frozen to the sys module.

https://github.com/python/cpython/blob/main/Lib/multiprocessing/context.py

def freeze_support(self):
    '''Check whether this is a fake forked process in a frozen executable.
    If so then run code specified by commandline and exit.
    '''
    if sys.platform == 'win32' and getattr(sys, 'frozen', False):
        from .spawn import freeze_support
        freeze_support()

所以,没设置 sys.frozen 的话,multiprocessing.freeze_support() 就不会起作用。

附一个复现该 Bug 的 case:

PyStand.int:

#  vim: set ts=4 sw=4 tw=0 et ft=python :
import multiprocessing
import sys, os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

import myprocess

if __name__ == '__main__':
    multiprocessing.freeze_support()
    multiprocessing.Process(target=myprocess.say_hello, daemon=True).start()

    app = QApplication([])

    win = QWidget()
    win.setWindowTitle('PyStand')

    layout = QVBoxLayout()

    label = QLabel('Hello, World !!')
    label.setAlignment(Qt.AlignCenter)
    layout.addWidget(label)

    btn = QPushButton(text = 'PUSH ME')
    layout.addWidget(btn)

    win.setLayout(layout)
    win.resize(400, 300)

    btn.clicked.connect(lambda : [
        print('exit'),
        win.close(),
    ])

    win.show()

    app.exec_()

myprocess.py:

def say_hello():
    print('Hello, world!')
rockswang commented 2 years ago

感谢支持!问题已经修复!