多线程 Python 海龟递归
Multithreading Python turtle with recursion
我做了一个 Python 海龟程序递归生成分形树,但是,由于通常需要几个小时才能完全绘制,我想尝试使用多线程让多个海龟一起工作。
我能够让两只乌龟同时移动,但是,在这个复杂得多的情况下,一切似乎都崩溃了。我已经尝试了很多不同的方法,并认为最终的解决方案就是那个,但它只会抛出一堆错误。
这是我的代码:
import turtle
import threading
from queue import Queue
class Location:
def __init__(self, xpos=0, ypos=0, heading=90):
self.xpos = xpos
self.ypos = ypos
self.heading = heading
def getx(self):
return self.xpos
def gety(self):
return self.ypos
def geth(self):
return self.heading
class Turtle(turtle.Turtle):
def tolocation(self, location):
self.penup()
self.setx(location.getx())
self.sety(location.gety())
self.setheading(location.geth())
self.pendown()
def get_location(self):
return Location(self.xcor(), self.ycor(), self.heading())
def draw_tree(self, startpos=Location(), size=100):
tm.q.put(self.tolocation(startpos))
for _ in range(size):
tm.q.put(self.forward(1))
for _ in range(45):
tm.q.put(self.right(1))
t2 = Turtle()
t2.speed(0)
tm.new_thread(t2.draw_tree, self.get_location(), size / 2)
for _ in range(90):
tm.q.put(self.left(1))
tm.new_thread(self.draw_tree, self.get_location(), size / 2)
class ThreadManager:
def __init__(self):
self.q = Queue()
self.threads = []
def new_thread(self, func, *args):
self.threads.append(threading.Thread(target=func, args=(args,)))
self.threads[-1].daemon = True
self.threads[-1].start()
def process_queue(self, scr):
while not self.q.empty():
(self.q.get())(1)
if threading.active_count() > 1:
scr.ontimer(self.process_queue(scr), 100)
tm = ThreadManager()
scr = turtle.Screen()
t1 = Turtle()
t1.speed(0)
tm.new_thread(t1.draw_tree)
tm.process_queue(scr)
scr.exitonclick()
谁能告诉我哪里出了问题?当 process_queue 调用自身时,错误消息说明递归太深了。我使用 scr.ontimer()
错了吗?
您的代码有几个问题:
draw_tree()
没有一个基本案例来阻止它(概念上的)
递归,它只是不断地创建新线程。
每次调用 draw_tree()
使用浮动将 size
分成两半
除法 range(size)
将失败,因为 range()
只能接受整数。
您对 self.get_location()
的调用无效,因为它告诉您
海龟在哪里,而不是一旦主线程它会在哪里
完成处理未完成的图形命令。你必须
计算你将在哪里,而不是看你在哪里。
此调用,tm.q.put(self.tolocation(startpos))
无效 -- 你
要么需要做 tm.q.put(self.tolocation, startpos)
要么打电话
tm.q.put()
在 self.tolocation()
.
中的每个命令
你不能在主线程之外的任何地方创建新海龟,因为它们
在创建时调用 tkinter,那将是错误的(不是主要的)
线。在我下面的返工中,我只是预先分配了它们。
args=(args,)
不正确——应该是 args=args
作为 args
已经是正确的格式。
不需要在每个分支点都新建两个线程,只需要
一。新龟走一条路,老龟继续前行
其他.
以下是我为解决上述问题和其他问题而对您的代码进行的修改:
import math
import threading
from queue import Queue
from turtle import Turtle, Screen
class Location:
def __init__(self, xpos=0, ypos=0, heading=90):
self.xpos = xpos
self.ypos = ypos
self.heading = heading
def clone(self):
return Location(self.xpos, self.ypos, self.heading)
class Terrapin(Turtle):
def tolocation(self, location):
tm.q.put((self.penup,))
tm.q.put((self.setx, location.xpos))
tm.q.put((self.sety, location.ypos))
tm.q.put((self.setheading, location.heading))
tm.q.put((self.pendown,))
def draw_tree(self, startpos, size=100):
if size < 1:
return
self.tolocation(startpos)
tm.q.put((self.forward, size))
angle = math.radians(startpos.heading)
startpos.xpos += size * math.cos(angle)
startpos.ypos += size * math.sin(angle)
tm.q.put((self.right, 45))
startpos.heading -= 45
tm.new_thread(pond.get().draw_tree, startpos.clone(), size / 2)
tm.q.put((self.left, 90))
startpos.heading += 90
self.draw_tree(startpos, size / 2)
pond.put(self) # finished with this turtle, return it to pond
class ThreadManager:
def __init__(self):
self.q = Queue()
self.threads = Queue()
def new_thread(self, method, *arguments):
thread = threading.Thread(target=method, args=arguments)
thread.daemon = True
thread.start()
self.threads.put(thread)
def process_queue(self):
while not self.q.empty():
command, *arguments = self.q.get()
command(*arguments)
if threading.active_count() > 1:
screen.ontimer(self.process_queue, 100)
screen = Screen()
# Allocate all the turtles we'll need ahead as turtle creation inside
# threads calls into Tk which fails if not running in the main thread
pond = Queue()
for _ in range(100):
turtle = Terrapin(visible=False)
turtle.speed('fastest')
pond.put(turtle)
tm = ThreadManager()
tm.new_thread(pond.get().draw_tree, Location())
tm.process_queue()
screen.exitonclick()
如果不是大步:
tm.q.put((self.right, 45))
您想将图形命令分解成小步骤:
for _ in range(45):
tm.q.put((self.right, 1))
没关系,我只是想获取 运行 的代码。你需要弄清楚这是否对你有任何好处。
我做了一个 Python 海龟程序递归生成分形树,但是,由于通常需要几个小时才能完全绘制,我想尝试使用多线程让多个海龟一起工作。
我能够让两只乌龟同时移动,但是,在这个复杂得多的情况下,一切似乎都崩溃了。我已经尝试了很多不同的方法,并认为最终的解决方案就是那个,但它只会抛出一堆错误。
这是我的代码:
import turtle
import threading
from queue import Queue
class Location:
def __init__(self, xpos=0, ypos=0, heading=90):
self.xpos = xpos
self.ypos = ypos
self.heading = heading
def getx(self):
return self.xpos
def gety(self):
return self.ypos
def geth(self):
return self.heading
class Turtle(turtle.Turtle):
def tolocation(self, location):
self.penup()
self.setx(location.getx())
self.sety(location.gety())
self.setheading(location.geth())
self.pendown()
def get_location(self):
return Location(self.xcor(), self.ycor(), self.heading())
def draw_tree(self, startpos=Location(), size=100):
tm.q.put(self.tolocation(startpos))
for _ in range(size):
tm.q.put(self.forward(1))
for _ in range(45):
tm.q.put(self.right(1))
t2 = Turtle()
t2.speed(0)
tm.new_thread(t2.draw_tree, self.get_location(), size / 2)
for _ in range(90):
tm.q.put(self.left(1))
tm.new_thread(self.draw_tree, self.get_location(), size / 2)
class ThreadManager:
def __init__(self):
self.q = Queue()
self.threads = []
def new_thread(self, func, *args):
self.threads.append(threading.Thread(target=func, args=(args,)))
self.threads[-1].daemon = True
self.threads[-1].start()
def process_queue(self, scr):
while not self.q.empty():
(self.q.get())(1)
if threading.active_count() > 1:
scr.ontimer(self.process_queue(scr), 100)
tm = ThreadManager()
scr = turtle.Screen()
t1 = Turtle()
t1.speed(0)
tm.new_thread(t1.draw_tree)
tm.process_queue(scr)
scr.exitonclick()
谁能告诉我哪里出了问题?当 process_queue 调用自身时,错误消息说明递归太深了。我使用 scr.ontimer()
错了吗?
您的代码有几个问题:
draw_tree()
没有一个基本案例来阻止它(概念上的) 递归,它只是不断地创建新线程。每次调用
draw_tree()
使用浮动将size
分成两半 除法range(size)
将失败,因为range()
只能接受整数。您对
self.get_location()
的调用无效,因为它告诉您 海龟在哪里,而不是一旦主线程它会在哪里 完成处理未完成的图形命令。你必须 计算你将在哪里,而不是看你在哪里。此调用,
tm.q.put(self.tolocation(startpos))
无效 -- 你 要么需要做tm.q.put(self.tolocation, startpos)
要么打电话tm.q.put()
在self.tolocation()
. 中的每个命令
你不能在主线程之外的任何地方创建新海龟,因为它们 在创建时调用 tkinter,那将是错误的(不是主要的) 线。在我下面的返工中,我只是预先分配了它们。
args=(args,)
不正确——应该是args=args
作为args
已经是正确的格式。不需要在每个分支点都新建两个线程,只需要 一。新龟走一条路,老龟继续前行 其他.
以下是我为解决上述问题和其他问题而对您的代码进行的修改:
import math
import threading
from queue import Queue
from turtle import Turtle, Screen
class Location:
def __init__(self, xpos=0, ypos=0, heading=90):
self.xpos = xpos
self.ypos = ypos
self.heading = heading
def clone(self):
return Location(self.xpos, self.ypos, self.heading)
class Terrapin(Turtle):
def tolocation(self, location):
tm.q.put((self.penup,))
tm.q.put((self.setx, location.xpos))
tm.q.put((self.sety, location.ypos))
tm.q.put((self.setheading, location.heading))
tm.q.put((self.pendown,))
def draw_tree(self, startpos, size=100):
if size < 1:
return
self.tolocation(startpos)
tm.q.put((self.forward, size))
angle = math.radians(startpos.heading)
startpos.xpos += size * math.cos(angle)
startpos.ypos += size * math.sin(angle)
tm.q.put((self.right, 45))
startpos.heading -= 45
tm.new_thread(pond.get().draw_tree, startpos.clone(), size / 2)
tm.q.put((self.left, 90))
startpos.heading += 90
self.draw_tree(startpos, size / 2)
pond.put(self) # finished with this turtle, return it to pond
class ThreadManager:
def __init__(self):
self.q = Queue()
self.threads = Queue()
def new_thread(self, method, *arguments):
thread = threading.Thread(target=method, args=arguments)
thread.daemon = True
thread.start()
self.threads.put(thread)
def process_queue(self):
while not self.q.empty():
command, *arguments = self.q.get()
command(*arguments)
if threading.active_count() > 1:
screen.ontimer(self.process_queue, 100)
screen = Screen()
# Allocate all the turtles we'll need ahead as turtle creation inside
# threads calls into Tk which fails if not running in the main thread
pond = Queue()
for _ in range(100):
turtle = Terrapin(visible=False)
turtle.speed('fastest')
pond.put(turtle)
tm = ThreadManager()
tm.new_thread(pond.get().draw_tree, Location())
tm.process_queue()
screen.exitonclick()
如果不是大步:
tm.q.put((self.right, 45))
您想将图形命令分解成小步骤:
for _ in range(45):
tm.q.put((self.right, 1))
没关系,我只是想获取 运行 的代码。你需要弄清楚这是否对你有任何好处。