python - PyInstaller 构建的 Windows EXE 因多处理而失败

标签 python windows python-2.7 multiprocessing pyinstaller

在我的项目中,我使用 Python 的 multiprocessing 库在 __main__ 中创建多个进程。该项目正在使用 PyInstaller 2.1.1 打包到单个 Windows EXE 中。

我像这样创建新流程:

from multiprocessing import Process
from Queue import Empty

def _start():
    while True:
        try:
            command = queue.get_nowait()
        # ... and some more code to actually interpret commands
        except Empty:
            time.sleep(0.015)

def start():
    process = Process(target=_start, args=args)
    process.start()
    return process

在 __main__ 中:

if __name__ == '__main__':
    freeze_support()

    start()

不幸的是,当将应用程序打包到 EXE 中并启动它时,我在这一行得到 WindowsError 5 或 6(似乎是随机的):

command = queue.get_nowait()

PyInstaller 主页上的一个秘诀声称,当将应用程序打包为单个文件时,我必须修改我的代码以在 Windows 中启用多处理。

我在这里重现代码:

import multiprocessing.forking
import os
import sys


class _Popen(multiprocessing.forking.Popen):
    def __init__(self, *args, **kw):
        if hasattr(sys, 'frozen'):
            # We have to set original _MEIPASS2 value from sys._MEIPASS
            # to get --onefile mode working.
            # Last character is stripped in C-loader. We have to add
            # '/' or '\\' at the end.
            os.putenv('_MEIPASS2', sys._MEIPASS + os.sep)
        try:
            super(_Popen, self).__init__(*args, **kw)
        finally:
            if hasattr(sys, 'frozen'):
                # On some platforms (e.g. AIX) 'os.unsetenv()' is not
                # available. In those cases we cannot delete the variable
                # but only set it to the empty string. The bootloader
                # can handle this case.
                if hasattr(os, 'unsetenv'):
                    os.unsetenv('_MEIPASS2')
                else:
                    os.putenv('_MEIPASS2', '')


class Process(multiprocessing.Process):
    _Popen = _Popen


class SendeventProcess(Process):
    def __init__(self, resultQueue):
        self.resultQueue = resultQueue

        multiprocessing.Process.__init__(self)
        self.start()

    def run(self):
        print 'SendeventProcess'
        self.resultQueue.put((1, 2))
        print 'SendeventProcess'


if __name__ == '__main__':
    # On Windows calling this function is necessary.
    if sys.platform.startswith('win'):
        multiprocessing.freeze_support()
    print 'main'
    resultQueue = multiprocessing.Queue()
    SendeventProcess(resultQueue)
    print 'main'

我对这个“解决方案”感到沮丧的是,第一,完全不清楚它到底在修补什么,第二,它以一种令人费解的方式编写,以至于无法推断出哪些部分是解决方案,哪些部分是解决方案只是一个例子。

任何人都可以分享一些关于这个问题的信息,并提供见解在一个项目中到底需要改变什么,以便在 PyInstaller 构建的单文件 Windows 可执行文件中启用多处理?

最佳答案

添加到尼古拉的回答...

*nix(Linux、Mac OS X 等)不需要对 PyInstaller 进行任何更改即可工作。 (这包括 --onedir--onefile 选项。)如果您只打算支持 *nix 系统,则无需担心这些。

但是,如果您计划支持 Windows,则需要添加一些代码,具体取决于您选择的选项:--onedir--onefile

如果您计划使用--onedir,您只需添加一个特殊的方法调用:

if __name__ == '__main__':
    # On Windows calling this function is necessary.
    multiprocessing.freeze_support()

根据文档,此调用必须在 if __name__ == '__main__': 之后立即进行,否则它将无法工作。 (强烈建议您在主模块中包含这两行。)

然而,实际上,您可以在调用之前进行检查,并且一切仍然有效:

if __name__ == '__main__':
    if sys.platform.startswith('win'):
        # On Windows calling this function is necessary.
        multiprocessing.freeze_support()

但是,在其他平台和情况下也可以调用 multiprocessing.freeze_support() - 运行它只会影响 Windows 上的卡住支持。如果您是字节码爱好者,您会注意到 if 语句添加了一些字节码,并且可以忽略使用 if 语句的潜在节省。因此,您应该坚持在 if __name__ == '__main__': 之后立即调用一个简单的 multiprocessing.freeze_support()

如果你打算使用--onefile,你需要添加nikola的代码:

import multiprocessing.forking
import os
import sys

class _Popen(multiprocessing.forking.Popen):
    def __init__(self, *args, **kw):
        if hasattr(sys, 'frozen'):
            # We have to set original _MEIPASS2 value from sys._MEIPASS
            # to get --onefile mode working.
            os.putenv('_MEIPASS2', sys._MEIPASS)
        try:
            super(_Popen, self).__init__(*args, **kw)
        finally:
            if hasattr(sys, 'frozen'):
                # On some platforms (e.g. AIX) 'os.unsetenv()' is not
                # available. In those cases we cannot delete the variable
                # but only set it to the empty string. The bootloader
                # can handle this case.
                if hasattr(os, 'unsetenv'):
                    os.unsetenv('_MEIPASS2')
                else:
                    os.putenv('_MEIPASS2', '')

class Process(multiprocessing.Process):
    _Popen = _Popen

# ...

if __name__ == '__main__':
    # On Windows calling this function is necessary.
    multiprocessing.freeze_support()

    # Use your new Process class instead of multiprocessing.Process

您可以将上面的内容与他的其余代码或以下内容结合起来:

class SendeventProcess(Process):
    def __init__(self, resultQueue):
        self.resultQueue = resultQueue

        multiprocessing.Process.__init__(self)
        self.start()

    def run(self):
        print 'SendeventProcess'
        self.resultQueue.put((1, 2))
        print 'SendeventProcess'

if __name__ == '__main__':
    # On Windows calling this function is necessary.
    multiprocessing.freeze_support()

    print 'main'
    resultQueue = multiprocessing.Queue()
    SendeventProcess(resultQueue)
    print 'main'

我从 here 得到代码,PyInstaller 的多处理配方的新站点。 (他们似乎已经关闭了基于 Trac 的网站。)

请注意,他们的 --onefile 多处理支持代码有一个小错误。他们将 os.sep 添加到他们的 _MEIPASS2 环境变量中。 (行:os.putenv('_MEIPASS2', sys._MEIPASS + os.sep))这打破了东西:

  File "<string>", line 1
    sys.path.append(r"C:\Users\Albert\AppData\Local\Temp\_MEI14122\")
                                                                    ^
SyntaxError: EOL while scanning string literal

Error when using os.sep in _MEIPASS2

我上面提供的代码是一样的,没有os.sep。删除 os.sep 可解决此问题,并允许使用 --onefile 配置进行多处理。

总结:

在 Windows 上启用 --onedir 多处理支持(不适用于 Windows 上的 --onefile,但在所有平台/配置上都是安全的):

if __name__ == '__main__':
    # On Windows calling this function is necessary.
    multiprocessing.freeze_support()

在 Windows 上启用 --onefile 多处理支持(在所有平台/配置上都是安全的,与 --onedir 兼容):

import multiprocessing.forking
import os
import sys

class _Popen(multiprocessing.forking.Popen):
    def __init__(self, *args, **kw):
        if hasattr(sys, 'frozen'):
            # We have to set original _MEIPASS2 value from sys._MEIPASS
            # to get --onefile mode working.
            os.putenv('_MEIPASS2', sys._MEIPASS)
        try:
            super(_Popen, self).__init__(*args, **kw)
        finally:
            if hasattr(sys, 'frozen'):
                # On some platforms (e.g. AIX) 'os.unsetenv()' is not
                # available. In those cases we cannot delete the variable
                # but only set it to the empty string. The bootloader
                # can handle this case.
                if hasattr(os, 'unsetenv'):
                    os.unsetenv('_MEIPASS2')
                else:
                    os.putenv('_MEIPASS2', '')

class Process(multiprocessing.Process):
    _Popen = _Popen

# ...

if __name__ == '__main__':
    # On Windows calling this function is necessary.
    multiprocessing.freeze_support()

    # Use your new Process class instead of multiprocessing.Process

来源:PyInstaller Recipe , Python multiprocessing docs

关于python - PyInstaller 构建的 Windows EXE 因多处理而失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24944558/

相关文章:

python - 使用 scipy 对稀疏矩阵进行 groupby

linux - 在 Windows 上使用 Mingw 时出错

python - 从二进制数据获取文件类型/扩展名

python - 通过 Tkinter 类传递数据帧

python - 使用 'u' 作为 json 加载 python 字符串

python - 在 pymongo 的 MongoClient() 中包含一个 key 文件

python - 在python 3.8.3上的IDE中导入pygame失败

python - 获取 Pygame 键盘输入并检查它是否为数字

windows - 在 Windows 中,2<&1 和 2>&1 有什么区别?

python - 在 Windows 中更改 Python 解释器时遇到问题