在使用约翰·泽尔的图形模块时的多线程(Python)
我已经使用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.Thread 的 target 参数中,你直接传入了类的实例,但它其实应该接受方法。
- 你可以传入
Particle.__init__()
或者Particle().move()
作为要执行的动作。 - 或者你可以先创建一个对象,比如
particle1 = Particle()
,然后使用它的方法,比如target=particle1.move()
。 - 如果你使用
daemon=True
,这意味着新创建的线程会在后台运行,主线程不需要等它完成。 - 如果你在线程实例上调用
join()
,比如thread1.join()
,主线程会等到那个特定的线程完成它的任务(在这种情况下,daemon=True
或False
都无所谓)。
这里我给你一些修正后的代码版本:
# ... 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/