在使用Python的PyInstaller、pymultiprogress、pyqt5等模块进行编程时,我发现了一个有趣的问题。当我在Windows系统下使用PyInstaller打包Python程序时,如果程序中使用了multiprocessing
模块,会出现子进程启动时弹出一个一模一样的窗体程序的情况。这个问题在我直接运行py代码时并未出现,但是在我使用pyinstaller打包成exe之后运行时,就会出现这个问题。这让我非常困惑,于是我开始寻找解决方法。
问题的出现
首先,我会展示一下问题的具体情况。以下是我在使用multiprocessing
模块时的源代码:
# 省略部分代码
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
当我运行这段代码时,一切都正常。
但是,当我使用PyInstaller将这段代码打包成exe文件后,运行这个exe文件,启动子进程时就会弹出一个一模一样的窗体程序。
问题的解决
经过一番搜索和尝试,我发现了解决这个问题的方法。那就是在程序的入口点代码中添加multiprocessing.freeze_support()
这一句。以下是修改后的代码:
if __name__ == "__main__":
multiprocessing.freeze_support()
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
添加multiprocessing.freeze_support()
这一句之后,我使用PyInstaller重新打包程序,运行程序,启动子进程正常,不再出现一个一模一样的窗体程序。
问题的原因
那么,为什么会出现这个问题呢?原因其实很简单。
在Windows系统下,multiprocessing模块创建子进程的方式是通过创建一个新的Python解释器进程,然后在新的解释器进程中运行程序的代码。这种方式在大多数情况下都能正常工作,但是在打包成exe文件的程序中,就会出现问题。
因为当打包成exe文件的程序启动时,会首先解压缩exe文件,然后启动Python解释器,接着运行程序的入口点代码。但是,由于multiprocessing模块创建的子进程会复制父进程的所有代码,包括启动应用程序的代码,所以子进程在启动时,也会解压缩exe文件,启动Python解释器,然后运行程序的入口点代码。这就导致了子进程启动时弹出一个一模一样的窗体程序。
而multiprocessing.freeze_support()
的作用就是在程序的入口点处告诉Python解释器,如果当前是在一个子进程中,就不要再运行程序的入口点代码。这样,子进程在启动时,就不会再弹出一个一模一样的窗体程序了。
所以,multiprocessing.freeze_support()
就像是一个开关,它告诉Python解释器,对于multiprocessing模块创建的子进程,如果是在一个打包成exe文件的程序中,就不要再运行程序的入口点代码。这就避免了子进程启动时弹出一个一模一样的窗体程序的问题。
总结
总的来说,添加multiprocessing.freeze_support()
这一句的作用是确保子进程在启动时不会重复执行程序的入口点代码,从而避免弹出一个一模一样的窗体程序。这是一个非常实用的技巧,可以帮助我们在使用PyInstaller打包Python程序时避免一些不必要的麻烦。
源代码(问题代码)如下:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QPushButton, QVBoxLayout, QWidget, QLabel
from PyQt5.QtCore import pyqtSignal, QThread
from PyQt5.QtGui import QColor, QTextCharFormat, QTextCursor
import serial_asyncio
import multiprocessing
from datetime import datetime
import asyncio
class ModbusProtocol(asyncio.Protocol):
def __init__(self, queue):
self.queue = queue
self.transport = None
def connection_made(self, transport):
self.transport = transport
def data_received(self, data):
hex_data = data.hex()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.queue.put(('Received', timestamp, hex_data))
reply = "01 83 02 C0 F1"
reply_bytes = bytes.fromhex(reply.replace(" ", ""))
self.transport.write(reply_bytes)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.queue.put(('Sent', timestamp, reply))
def run_modbus_tool(queue):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
coro = serial_asyncio.create_serial_connection(loop, lambda: ModbusProtocol(queue), "COM13", 115200)
loop.run_until_complete(coro)
loop.run_forever()
loop.close()
class UpdateUIThread(QThread):
updateSignal = pyqtSignal(str, str, QColor)
def __init__(self, queue):
super().__init__()
self.queue = queue
def run(self):
while True:
if not self.queue.empty():
action, timestamp, data = self.queue.get()
color = QColor("blue") if action == "Received" else QColor("purple")
self.updateSignal.emit(timestamp, data, color)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("ModbusRTU从站工具")
self.setGeometry(100, 100, 400, 300)
self.textEdit = QTextEdit()
self.textEdit.setReadOnly(True)
self.startButton = QPushButton("启动")
self.stopButton = QPushButton("停止")
self.startButton.clicked.connect(self.startCommunication)
self.stopButton.clicked.connect(self.stopCommunication)
self.portLabel = QLabel("通讯端口:COM13")
self.baudrateLabel = QLabel("波特率:115200")
self.databitsLabel = QLabel("数据位:8位")
self.parityLabel = QLabel("奇偶检验:None")
self.stopbitsLabel = QLabel("停止位:1")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.startButton)
layout.addWidget(self.stopButton)
layout.addWidget(self.portLabel)
layout.addWidget(self.baudrateLabel)
layout.addWidget(self.databitsLabel)
layout.addWidget(self.parityLabel)
layout.addWidget(self.stopbitsLabel)
centralWidget = QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
self.queue = multiprocessing.Queue(maxsize=100)
self.updateThread = UpdateUIThread(self.queue)
self.updateThread.updateSignal.connect(self.displayData)
self.updateThread.start()
def startCommunication(self):
self.process = multiprocessing.Process(target=run_modbus_tool, args=(self.queue,))
self.process.start()
self.appendText("通讯已启动", QColor("green"))
def stopCommunication(self):
self.process.terminate()
self.appendText("通讯已停止", QColor("red"))
def displayData(self, timestamp: str, data: str, color: QColor):
action = "接收数据:" if color == QColor("blue") else "发送:"
self.appendText(f"{timestamp}_{action} {data}", color)
def appendText(self, text: str, color: QColor):
text_format = QTextCharFormat()
text_format.setForeground(color)
cursor = self.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text + "\n", text_format)
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())