2014-07-24 16 views
10

Trong dự án của tôi, tôi đang sử dụng thư viện multiprocessing của Python để tạo nhiều quy trình trong __main__. Dự án đang được đóng gói thành một Windows EXE duy nhất sử dụng PyInstaller 2.1.1.Windows EXE PyInstaller được xây dựng không thành công với đa xử lý

tôi có thể tạo quy trình mới như sau:

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 

Và trong __main__:

if __name__ == '__main__': 
    freeze_support() 

    start() 

Thật không may, khi đóng gói ứng dụng thành một EXE và khởi chạy nó, tôi nhận được WindowsError 5 hoặc 6 (dường như ngẫu nhiên) tại dòng này:

command = queue.get_nowait() 

Công thức tại trang chủ của PyInstaller cho rằng Tôi phải sửa đổi mã của mình để cho phép đa xử lý trong Windows khi đóng gói ứng dụng dưới dạng một tệp.

Tôi đang tái tạo mã ở đây:

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' 

thất vọng của tôi với điều này "giải pháp" là, một, nó hoàn toàn không rõ ràng chính xác những gì nó được vá, và, hai, rằng nó được viết bằng như một cách phức tạp mà nó trở thành không thể suy ra phần nào là giải pháp, và đó chỉ là một minh họa.

Bất kỳ ai cũng có thể chia sẻ một chút về vấn đề này và cung cấp thông tin chi tiết về chính xác những gì cần được thay đổi trong một dự án cho phép đa xử lý trong tệp thực thi Windows đơn tệp PyInstaller?

+0

Công thức có khắc phục được sự cố không? – dano

+0

Vâng, không rõ ràng (ít nhất là với tôi) cách áp dụng công thức. Chỉ cần dán mã ở trên trong tập lệnh Python chính của tôi cũng không hoạt động, vì nó làm tăng thêm hai ngoại lệ không liên quan đến các kịch bản lệnh Python của tôi. Mà nói với tôi rằng công thức về cơ bản là thiếu sót. – nikola

+0

Nếu bạn chỉ chạy công thức dưới dạng tập lệnh độc lập, nó có chạy không có lỗi không? – dano

Trả lời

6

Trả lời câu hỏi của riêng tôi sau khi tìm thấy this PyInstaller ticket:

Rõ ràng tất cả chúng ta phải làm là cung cấp một lớp Process (và _Popen) như hình dưới đây, và sử dụng nó thay vì multiprocessing.Process. Tôi đã sửa chữa và đơn giản hóa các lớp để làm việc trên Windows chỉ, * ix hệ thống có thể cần mã khác nhau.

Vì lợi ích của sự hoàn chỉnh, đây là mẫu chuyển thể từ câu hỏi trên:

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

Liên kết vé không còn giá trị. Các tài liệu hiện tại cho điều này là ở đây: https://github.com/pyinstaller/pyinstaller/wiki/Recipe-Multiprocessing – tom10

10

Để thêm vào câu trả lời của Nikola ...

* nix (Linux, Mac OS X, vv) KHÔNG yêu cầu bất kỳ thay đổi nào cho PyInstaller hoạt động. (Điều này bao gồm cả hai tùy chọn --onedir--onefile.) Nếu bạn chỉ định hỗ trợ các hệ thống * nix, không cần phải lo lắng về bất kỳ điều nào trong số này.

Tuy nhiên, nếu bạn dự định hỗ trợ Windows, bạn sẽ cần phải thêm một số mã, tùy thuộc vào tùy chọn bạn chọn: --onedir hoặc --onefile.

Nếu bạn có kế hoạch để sử dụng --onedir, tất cả các bạn sẽ cần phải thêm là một phương pháp gọi đặc biệt:

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

Theo tài liệu, cuộc gọi này phải được thực hiện ngay sau if __name__ == '__main__':, nếu không nó sẽ không làm việc. (Bạn nên có hai dòng này trong mô-đun chính của mình.)

Trên thực tế, tuy nhiên, bạn có thể đủ khả năng để làm một kiểm tra trước khi cuộc gọi, và những thứ vẫn sẽ làm việc:

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

Tuy nhiên, gọi multiprocessing.freeze_support() có thể trên nền tảng và các tình huống khác nữa - chạy nó chỉ ảnh hưởng đến sự hỗ trợ đóng băng trên Windows. Nếu bạn là một nut bytecode, bạn sẽ nhận thấy rằng câu lệnh if thêm một số bytecode, và tiết kiệm tiềm năng từ việc sử dụng một câu lệnh if không đáng kể. Do đó, bạn chỉ cần thực hiện cuộc gọi multiprocessing.freeze_support() đơn giản ngay sau if __name__ == '__main__':.

Nếu bạn có kế hoạch để sử dụng --onefile, bạn sẽ cần phải thêm mã Nikola của:

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 

Bạn có thể kết hợp trên với phần còn lại của mã của mình, hoặc những điều sau:

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' 

tôi lấy mã từ here, trang web mới của PyInstaller cho công thức đa xử lý. (Dường như họ đã tắt trang web dựa trên Trac của họ.)

Lưu ý rằng họ có lỗi nhỏ với mã của họ cho hỗ trợ đa xử lý --onefile. Họ thêm os.sep vào biến môi trường _MEIPASS2 của họ. (Line: os.putenv('_MEIPASS2', sys._MEIPASS + os.sep)) này phá vỡ điều:

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

Code tôi cung cấp ở trên là như nhau, không có sự os.sep. Loại bỏ các os.sep sửa chữa vấn đề này và cho phép đa xử lý để làm việc bằng cách sử dụng cấu hình --onefile.

Nói tóm lại:

Cho phép hỗ trợ --onedir đa trên Windows (KHÔNG làm việc với --onefile trên Windows, nhưng nếu không an toàn trên tất cả các nền tảng/cấu hình):

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

Kích hoạt --onefile hỗ trợ đa xử lý trên Windows (an toàn trên tất cả các nền tảng/cấu hình, tương thích với --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 

Nguồn: PyInstaller Recipe, Python multiprocessing docs

+1

Cảm ơn câu trả lời chi tiết. Tôi đã có vấn đề với các chủ đề zombie xuất hiện sau khi tôi đóng cửa sổ Python chính của tôi (với tk) khi tôi sử dụng _-- onefile_ tùy chọn. Đoạn mã cuối cùng mà bạn xác định lại Popen đã khắc phục sự cố. Đối với bất kỳ ai làm việc với Python> 3.4, bạn cần sử dụng 'nhập multiprocessing.popen_spawn_win32 như forking' thay vì' multiprocessing.forking'. –

+0

Đừng quên rằng 'multiprocessing.freeze_support()' phải luôn là dòng đầu tiên trong '__name__ == '__main __'' và không nên có mã nào khác được thực hiện trước dòng này (tức là trước '__name__ == '__main__ ''). Tôi đã có một số nhập khẩu thực hiện một số mã, dẫn đến 'multiprocessing.freeze_support()' không có bất kỳ hiệu ứng nào. – Guido

Các vấn đề liên quan