PyInstaller构buildWindows EXE失败,多处理

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

我创build了如下的新进程:

 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 (eg 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' 

我对这个“解决scheme”感到沮丧的是,其中一个,它究竟是在修补什么,还有两个,它是以这样一个复杂的方式写的,所以不可能推断哪个部分是解决scheme,哪个只是一个插图。

任何人都可以分享一些关于这个问题的信息,并且提供一个项目中确切需要改变的东西,这个项目能够在PyInstaller构build的单文件Windows可执行文件中进行多处理。

添加到nikola的答案…

* 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 (eg 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' 

我从这里得到了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 

在_MEIPASS2中使用os.sep时出错

我上面提供的代码是一样的,没有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 (eg 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食谱 , Python多处理文档

找到这个PyInstaller票据后回答我自己的问题:

显然我们所要做的就是提供一个如下所示的Process (和_Popen )类,并使用它来代替multiprocessing.Process 我已经更正并简化了这个类,只能在Windows上工作,* ix系统可能需要不同的代码。

为了完整起见,下面是上述问题的改编样本:

 import multiprocessing from Queue import Empty class _Popen(multiprocessing.forking.Popen): def __init__(self, *args, **kw): if hasattr(sys, 'frozen'): os.putenv('_MEIPASS2', sys._MEIPASS) try: super(_Popen, self).__init__(*args, **kw) finally: if hasattr(sys, 'frozen'): os.unsetenv('_MEIPASS2') class Process(multiprocessing.Process): _Popen = _Popen 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