在我的项目中,我使用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可执行文件中启用多处理的项目中确切需要更改的内容吗?
答案 0 :(得分:11)
再加上尼古拉的答案......
* 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语句可以忽略不计。因此,您应该在multiprocessing.freeze_support()
之后立即坚持一个简单的if __name__ == '__main__':
电话。
如果您打算使用--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'
我从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
我上面提供的代码是相同的,没有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
答案 1 :(得分: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