Python多进程结合tkinter出现无法pickle '_tkinter.tkapp'对象错误求助
问题分析与解决
首先看你遇到的核心错误:TypeError: cannot pickle '_tkinter.tkapp' object,这是因为Windows系统下multiprocessing默认用spawn启动子进程,这种方式会尝试序列化(pickle)整个进程目标所属的对象(也就是你的GUI实例),但Tkinter的窗口对象(_tkinter.tkapp)是无法被序列化的,直接把GUI类的方法作为进程目标就会触发这个问题。
另外你的代码还有几个隐藏问题:
- 初始化进程时,
self.p1被连续赋值两次,导致recvData的进程直接被覆盖,self.p2根本没定义,运行时会报错 recvData函数里直接使用了getData中的ssocket对象,子进程/线程无法访问这个局部变量- Tkinter的GUI操作必须在主线程执行,你在子进程的
calculate_threshold里直接修改红绿灯颜色,这本身就是不安全的行为
解决方案
推荐改用threading替代multiprocessing,因为你的任务以IO(网络接收)和轻量计算为主,线程足够胜任,而且线程共享内存空间,不会有序列化问题,也能安全使用线程安全的queue.Queue。同时要把GUI更新的逻辑放到主线程执行。
下面是修改后的完整代码:
import socket import turtle import queue import threading from tkinter import * class GUI: def __init__(self, master): self.master = master master.title("Collision Detection") self.buff_data = queue.Queue() # 初始化线程(不再用multiprocessing) self.recv_thread = None self.calc_thread = None self.running = False # 标记线程是否在运行 self.input_label = Label(root, text = "Input all the gratings set straight wavelength values in nm") self.input_label.grid(row = 0) self.core_string = "Core " self.entries = [] self.label_col_inc = 0 self.entry_col_inc = 1 self.core_range = range(1, 5) for y in self.core_range: self.core_text = self.core_string + str(y) + '_' + '25' self.core_label = Label(root, text = self.core_text) self.entry = Entry(root) self.core_label.grid(row=1, column=self.label_col_inc, sticky=E) self.entry.grid(row=1, column=self.entry_col_inc) self.entries.append(self.entry) self.label_col_inc += 2 self.entry_col_inc += 2 self.threshold_label = Label(root, text = "Threshold in nm") self.entry_threshold = Entry(root) self.threshold_label.grid(row = 2, sticky = E) self.entry_threshold.grid(row = 2, column = 1) self.light_label = Label(root, text = 'Status') self.light_label.grid(row = 3, column = 3) self.canvas = Canvas(root, width = 150, height = 50) self.canvas.grid(row = 4, column = 3) # Green light self.green_light = turtle.RawTurtle(self.canvas) self.green_light.shape('circle') self.green_light.color('grey') self.green_light.penup() self.green_light.goto(0,0) # Red light self.red_light = turtle.RawTurtle(self.canvas) self.red_light.shape('circle') self.red_light.color('grey') self.red_light.penup() self.red_light.goto(40,0) self.data_button = Button(root, text = "Get data above threshold", command = self.start_data_processing) self.data_button.grid(row = 5, column = 0) def start_data_processing(self): if self.running: return # 避免重复启动线程 self.running = True # 启动接收线程和计算线程 self.recv_thread = threading.Thread(target=self.recvData) self.calc_thread = threading.Thread(target=self.calculate_threshold) self.recv_thread.daemon = True self.calc_thread.daemon = True self.recv_thread.start() self.calc_thread.start() # function to receive TCP data blocks def recvData(self): host = '127.0.0.1' port = 5000 try: s = socket.socket() s.connect((host, port)) while self.running: len_message = s.recv(4) if not len_message: break # 连接断开 bytes_length = int(len_message.decode('utf-8')) recvd_data = s.recv(bytes_length) if recvd_data: self.buff_data.put(recvd_data) except Exception as e: print(f"接收数据出错: {e}") finally: s.close() self.running = False def calculate_threshold(self): while self.running: try: rmv_data = self.buff_data.get(timeout=1) # 设置超时,方便检测停止信号 except queue.Empty: continue stringdata = rmv_data.decode('utf-8') rep_str = stringdata.replace(",", ".") splitstr = rep_str.split() # received wavelength values inc = 34 wav_threshold = [] for y in self.entries: straight_wav = float(y.get()) wav = float(splitstr[inc]) wav_diff = abs(wav - straight_wav) # 用abs简化取绝对值 wav_threshold.append(wav_diff) inc += 56 threshold = float(self.entry_threshold.get()) # 判断状态,准备更新GUI has_over_threshold = any(diff > threshold for diff in wav_threshold) # 用after把GUI更新放到主线程执行 self.master.after(0, self.update_status_light, has_over_threshold) # 准备写入文件的数据 data = [splitstr[0], splitstr[1]] inc1 = 0 for x in wav_threshold: if x > threshold: data.append(splitstr[34 + inc1]) data.append(str(x)) else: data.append('-') data.append('-') inc1 += 56 self.write_file(data) def update_status_light(self, has_over_threshold): # 主线程中更新红绿灯 if has_over_threshold: self.red_light.color('red') self.green_light.color('grey') else: self.red_light.color('grey') self.green_light.color('green') # function to write into the file def write_file(self,data): with open("Output.txt", "a") as text_file: text_file.write('\t'.join(data)) text_file.write('\n') if __name__ == '__main__': root = Tk() gui = GUI(root) root.mainloop()
修改要点说明
- 替换multiprocessing为threading:避免了序列化Tkinter对象的问题,线程共享内存,Queue可以直接使用
- 线程生命周期管理:添加
self.running标记,控制线程的启动和停止,避免重复启动 - GUI更新移到主线程:用
self.master.after(0, ...)把红绿灯颜色更新的逻辑放到主线程执行,符合Tkinter的线程安全要求 - 修正socket作用域问题:把socket的创建和连接移到
recvData线程内部,避免跨线程访问局部变量 - 简化代码逻辑:用
abs()简化绝对值计算,优化数据拼接的方式
内容的提问来源于stack exchange,提问作者Newbie
相关产品推荐
相关产品推荐

