복붙노트

[PYTHON] PyInstaller로 빌드 된 Windows EXE가 다중 처리와 함께 실패합니다.

PYTHON

PyInstaller로 빌드 된 Windows EXE가 다중 처리와 함께 실패합니다.

내 프로젝트에서는 파이썬 다중 처리 라이브러리를 사용하여 __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 실행 파일에서 멀티 프로세싱을 가능하게하는 프로젝트에서 정확하게 변경해야 할 사항을 파악할 수 있습니까?

해결법

  1. ==============================

    1.nikola의 답변에 추가하려면 ...

    nikola의 답변에 추가하려면 ...

    * nix (Linux, Mac OS X 등)는 PyInstaller가 작동하기 위해 어떤 변경도 요구하지 않습니다. (여기에는 --onedir 옵션과 --onefile 옵션이 모두 포함됩니다.) * nix 시스템 만 지원하려는 경우, 이것에 대해 걱정할 필요가 없습니다.

    그러나 Windows 지원을 계획하고 있다면 --onerir 또는 --onefile과 같이 어떤 옵션을 선택 하느냐에 따라 코드를 추가해야합니다.

    --onedir을 사용할 계획이라면 특별한 메서드 호출 만 추가하면됩니다 :

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

    문서에 따르면이 호출은 __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 문을 무시해도 잠재적 인 비용 절감 효과가 있음을 알 수 있습니다. 따라서 __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'
    

    여기 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
    

    위에 제공된 코드는 os.sep없이 동일합니다. os.sep를 제거하면이 문제가 해결되고 --onefile 구성을 사용하여 다중 처리가 작동합니다.

    요약하자면:

    Windows에서 --onedir 다중 처리 지원 활성화 (Windows에서는 --onefile에서는 작동하지 않지만 모든 플랫폼 / 구성에서 안전함)

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

    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 Recipe, Python 멀티 프로세싱 문서

  2. ==============================

    2.이 PyInstaller 티켓을 찾은 후에 내 질문에 답하십시오.

    이 PyInstaller 티켓을 찾은 후에 내 질문에 답하십시오.

    우리가해야 할 일은 아래와 같이 Process (및 _Popen) 클래스를 제공하고 다중 처리 대신 사용하는 것입니다. 프로세스. 나는 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
    
  3. from https://stackoverflow.com/questions/24944558/pyinstaller-built-windows-exe-fails-with-multiprocessing by cc-by-sa and MIT license