2014-07-24 57 views
10

在我的项目我使用Python的multiprocessing库创建于__main__多个进程。该项目使用PyInstaller 2.1.1打包到单个Windows EXE中。PyInstaller一个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可执行文件改变什么呢?

+0

是否配方解决这个问题? – dano

+0

那么,目前还不清楚(至少对我来说)如何应用配方。只是将上面的代码粘贴到我的主Python脚本中也不起作用,因为它会引发两个与我的Python脚本无关的异常。这告诉我这个配方是根本上有缺陷的。 – nikola

+0

如果您只是将配方作为独立脚本运行,它是否运行没有错误? – dano

回答

6

回答我自己的问题找到this PyInstaller ticket后:

显然,所有我们需要做的是提供一个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 
+1

门票链接不再有效。目前的文档在这里:https://github.com/pyinstaller/pyinstaller/wiki/Recipe-Multiprocessing – tom10

10

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

* 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,你将需要添加尼古拉代码:

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' 

我从PyInstaller的多处理配方新站点获得了代码here。 (他们似乎已经关闭了他们的基于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多支持(--onefile在Windows上不起作用,但在所有平台上,否则安全/配置):

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

上启用--onefile多支持Windows(在所有平台/配置上安全,与--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 RecipePython multiprocessing docs

+1

感谢您的详细解答。当我使用_-- onefile_选项关闭我的主Python窗口(使用tk)后,出现了僵尸线程问题。您重新定义Popen的最后一个片段解决了问题。 对于任何使用Python> 3.4的人,您需要使用'import multiprocessing.popen_spawn_win32 for fork'而不是'multiprocessing.forking'。 –

+0

不要忘记''multiprocessing.freeze_support()'应该总是'__name__ =='__main __''中的第一行,并且在该行之前不应该有其他代码执行(即在__name__ =='__main__之前“')。我有一些导入执行了一些代码,导致'multiprocessing.freeze_support()'没有任何效果。 – Guido