在使用约翰·泽尔的图形模块时的多线程(Python)

0 投票
2 回答
28 浏览
提问于 2025-04-14 17:33

我已经使用John Zelle的图形模块有一段时间了,不过这是我第一次尝试使用线程模块。我想让多个对象(圆形)在屏幕上同时移动,而且它们的起始位置不同。最终的代码还会包含递归和跳转到多个函数的功能。

这是我想要实现的一个例子:

from graphics import *
from random import randint
import time
import threading

class Particle:
    
    def __init__(self):
        self.xpos   = randint(0,50)
        self.ypos   = randint(0,50)
        
        self.graphic = Circle(Point(self.xpos,self.ypos),5)    
        self.graphic.draw(window)
        
    def move(self):
        for step in range(5):
            self.xpos += 1
            self.ypos += 1
            self.graphic.move(self.xpos,self.ypos)
            time.sleep(0.5)
            
window = GraphWin("window",500,500)

threading.Thread(target=Particle()).start()
threading.Thread(target=Particle()).start()

在尝试使用线程的过程中,我遇到了两个不同的错误。

第一个错误是(来自上面的代码):

Exception in thread Thread-1:
Traceback (most recent call last):
Thread-2  File "/usr/lib/python3.11/threading.py", line 1038, in _bootstrap_inner
:
    Traceback (most recent call last):
  File "/usr/lib/python3.11/threading.py", line 1038, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.11/threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
TypeError: 'Particle' object is not callable
self.run()
  File "/usr/lib/python3.11/threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
TypeError: 'Particle' object is not callable

还有一个错误是:"RuntimeError: main thread is not in main loop"

我尝试了很多来自Stack Overflow和其他网站的建议,比如:

  • 把我的线程设置为'daemon = True'
  • 使用'plt.switch_backend('agg')'
  • 创建一个单独的线程类
  • 给我的线程一个句柄,然后在这个句柄上使用.start

注意:如果你确定这些方法应该有效,请告诉我该怎么做,因为我对使用线程模块还很陌生。

谢谢你的帮助!

2 个回答

0

这段内容有点复杂,但我觉得它能给你一个可以开始的基础:

from queue import Queue
from random import randint
from threading import Thread
from time import sleep
from graphics import *

class Particle:
    def __init__(self):
        xpos = randint(0, 50)
        ypos = randint(0, 50)

        self.graphic = Circle(Point(xpos, ypos), 5)
        self.graphic.draw(window)

    def move(self):
        while True:
            graphic_commands.put((self.graphic.move, randint(1, 5), randint(1, 5)))
            sleep(0.25)  # sleep *this* thread

def process_queue():
    while not graphic_commands.empty():
        graphic, *arguments = graphic_commands.get()
        graphic(*arguments)

    window.after(100, process_queue)

graphic_commands = Queue(1)  # size = ~ number of hardware threads you have - 1

window = GraphWin("window", 500, 500)

process_queue()

Thread(target=Particle().move, daemon=True).start()
Thread(target=Particle().move, daemon=True).start()
Thread(target=Particle().move, daemon=True).start()

window.getMouse()
window.close()

这个方法是我之前为turtle做的一个类似解决方案,但这次加入了一些新的变化,适用于graphics.py。这里用到的(线程安全的)队列是为了确保所有线程的绘图请求都在tkinter的主线程中执行。

0

我试了你的代码,发现需要一些修正:

  • threading.Threadtarget 参数中,你直接传入了类的实例,但它其实应该接受方法。
  • 你可以传入 Particle.__init__() 或者 Particle().move() 作为要执行的动作。
  • 或者你可以先创建一个对象,比如 particle1 = Particle(),然后使用它的方法,比如 target=particle1.move()
  • 如果你使用 daemon=True,这意味着新创建的线程会在后台运行,主线程不需要等它完成。
  • 如果你在线程实例上调用 join(),比如 thread1.join(),主线程会等到那个特定的线程完成它的任务(在这种情况下,daemon=TrueFalse 都无所谓)。

这里我给你一些修正后的代码版本:

# ... same other code

threading.Thread(target=Particle().__init__()).start()
threading.Thread(target=Particle().__init__()).start()

或者

# ... same other code

threading.Thread(target=Particle().move()).start()
threading.Thread(target=Particle().move()).start()

或者

# ... same other code

particle1 = Particle()
particle2 = Particle()

threading.Thread(target=particle1.move()).start()
threading.Thread(target=particle2.move()).start()

或者

# ... same upper code

particle1 = Particle()
particle2 = Particle()

thread1 = threading.Thread(target=particle1.move()).start()
thread2 = threading.Thread(target=particle2.move()).start()

thread1.join()
thread2.join()

在最后一段代码中,你的窗口会一直打开,直到你的移动函数执行完毕,或者你手动关闭它。

如果你想更深入了解 Python 的线程,可以查看这个指南: https://superfastpython.com/threading-in-python/

撰写回答