You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

类中使用Python Multiprocessing无法充分利用CPU的问题求助

解决多进程CPU利用率不足的问题

兄弟,我仔细看了你的代码,发现几个核心问题导致多进程没真正跑起来,CPU利用率上不去,咱们一个个来捋:

1. 最致命的错误:进程创建时直接执行了目标函数

你创建Process的时候写的是:

p = multiprocessing.Process(target=self.run(proc))

这里self.run(proc)是直接在主进程里调用了run方法,而不是把run作为子进程的目标函数!正确的写法应该是把函数对象传给target,参数用args传递:

p = multiprocessing.Process(target=self.run, args=(proc,))

不然子进程根本没干活,所有计算都在主进程串行执行,CPU当然跑不满。

2. 进程串行执行:join()导致一次只能跑一个进程

你的spawn方法里,启动每个进程后立刻调用p.join(),这会阻塞当前线程,必须等这个进程完全结束,才会继续找下一个空闲进程位。相当于你每次只能跑一个进程,完全没用到多进程并行的优势!

3. 子进程直接操作GUI:tkinter不支持跨进程操作

tkinter的所有GUI元素只能在主进程的主线程里操作,子进程直接调用self.update修改Text组件是完全不安全的,会导致程序崩溃或者奇怪的行为。必须用进程间通信(比如队列)让子进程发送消息,主进程自己来更新GUI。

4. 进程状态管理失效:子进程修改的字典不会同步到主进程

self.processes是主进程的字典,子进程启动时会复制一份这个字典,所以子进程里修改self.processes['1'] = 0根本不会影响主进程的状态,主进程永远不知道这个进程已经结束了。


修改后的完整代码示例

我把这些问题都修复了,你可以参考:

import tkinter as tk
from tkinter import ttk, filedialog
import multiprocessing
import time
from queue import Empty

class main():
    def __init__(self,master):
        self.processes = {'1': None, '2': None, '3': None, '4': None, '5': None, '6': None}
        self.master = master
        self.master.title("Framework")
        self.master.minsize(1450, 650)
        self.master.maxsize(1450,650)
        style = ttk.Style()
        style.theme_use('clam')
        style.configure('Custom.TButton',background='#94FDFA')
        
        # 进程间通信队列:子进程发消息,主进程更新GUI
        self.msg_queue = multiprocessing.Queue()
        
        # GUI元素部分保留你的原有布局,简化重复代码
        self.Box4 = Entry(self.master)
        self.text_frames = {}
        positions = [
            (450, 80, 300, 250), (775, 80, 300, 250), (1100, 80, 300, 250),
            (450, 370, 300, 250), (775, 370, 300, 250), (1100, 370, 300, 250)
        ]
        for idx, (x, y, w, h) in enumerate(positions, 1):
            text = Text(self.master)
            scroll = Scrollbar(self.master, command=text.yview)
            text.configure(yscrollcommand=scroll.set)
            text.tag_configure('bold_italics', font=('Verdana', 12, 'bold', 'italic'))
            text.place(x=x, y=y, bordermode="inside", width=w, height=h)
            scroll.place(x=x+w, y=y, height=h)
            self.text_frames[str(idx)] = text
        
        self.Box4.place(x=10, y=10, bordermode="inside", width=820, height=35)
        self.b1 = ttk.Button(self.master,text = "Folder", command=self.select_folder)
        self.b1.place(x=850, y=10)
        self.b2 = ttk.Button(self.master, text="run", command=self.spawn)
        self.b2.place(x=20, y=600)
        self.master.geometry('%dx%d+%d+%d' % (1450, 650, 20, 20))
        
        # 启动主进程的消息监听任务
        self.check_queue()

    def check_queue(self):
        """主进程定时检查队列,更新GUI和进程状态"""
        try:
            while not self.msg_queue.empty():
                msg = self.msg_queue.get_nowait()
                proc_id = msg['proc_id']
                content = msg['content']
                if content == "finished":
                    # 进程结束,更新状态
                    self.processes[proc_id] = None
                    self.text_frames[proc_id].insert(tk.END, "任务完成!\n")
                else:
                    # 更新进度消息
                    self.text_frames[proc_id].insert(tk.END, content + '\n')
                self.text_frames[proc_id].see(tk.END)
        except Empty:
            pass
        # 每500ms检查一次队列
        self.master.after(500, self.check_queue)

    def select_folder(self):
        mas = tk.Toplevel()
        mas.withdraw()
        dir_path = filedialog.askdirectory()
        self.Box4.insert(0, str(dir_path))
        self.dir_path = dir_path

    def spawn(self):
        """启动空闲的进程,并行执行"""
        # 找到第一个空闲的进程位
        for proc_id in self.processes:
            if self.processes[proc_id] is None:
                # 正确创建子进程:传递函数和参数
                p = multiprocessing.Process(target=self.run, args=(proc_id, self.msg_queue))
                p.start()
                # 保存进程对象,方便后续管理(比如终止)
                self.processes[proc_id] = p
                self.text_frames[proc_id].delete('1.0', tk.END)
                self.text_frames[proc_id].insert(tk.END, "启动任务...\n")
                break

    @staticmethod
    def run(proc_id, msg_queue):
        """子进程的计算任务,只负责发消息,不碰GUI"""
        # 这里替换成你实际的计算逻辑
        steps = [
            ("done 1/10", 2),
            ("done 2/10", 5),
            ("done 3/10", 3),
            ("done 4/10", 8),
            ("done 5/10", 2)
        ]
        for content, sleep_time in steps:
            time.sleep(sleep_time)
            # 发送进度消息到队列
            msg_queue.put({'proc_id': proc_id, 'content': content})
        # 任务完成,发送结束消息
        msg_queue.put({'proc_id': proc_id, 'content': "finished"})

if __name__ == '__main__':
    # Windows下多进程必须在if __name__ == '__main__'里启动
    multiprocessing.freeze_support()
    master = tk.Tk()
    ma = main(master)
    master.mainloop()

关键修改点说明

  • 进程创建方式:改用target=self.run, args=(proc_id, self.msg_queue),确保子进程真正执行计算任务。
  • 并行执行:spawn方法去掉了join(),启动进程后直接继续,多个进程可以同时运行。
  • 进程间通信:用multiprocessing.Queue传递消息,子进程只负责发消息,主进程通过check_queue定时读取并更新GUI,完全符合tkinter的线程/进程安全要求。
  • 进程状态管理:主进程保存子进程对象,通过队列消息得知进程结束,更新self.processes状态。
  • 简化代码:把重复的Text组件创建逻辑用循环简化,减少冗余。

这样修改后,你应该能看到CPU利用率明显上升,多个进程并行执行计算任务了。

内容的提问来源于stack exchange,提问作者user10304383

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:39:09