Reputation: 4695
Is there a way to use two turtles at the same time to draw two circles at the same time in one window? I tried this code but two turtles draw in separated windows
from multiprocessing import Process
import turtle
t1=turtle.Turtle()
t2=turtle.Turtle()
def tes1():
t1.speed(0)
i=0
while i < 360:
t1.forward(1)
t1.left(1)
i+=1
def tes2():
t2.speed(0)
i=0
while i < 360:
t2.forward(1)
t2.right(1)
i+=1
if __name__ == '__main__':
p1 = Process(target=tes1)
p1.start()
p2 = Process(target=tes2)
p2.start()
p1.join()
p2.join()
but somebody told me try multithreading but this code has a bad semantic error!!
import threading
import turtle
t1=turtle.Turtle()
t2=turtle.Turtle()
def tes1():
t1.speed(0)
i=0
while i < 360:
t1.forward(1)
t1.left(1)
i+=1
def tes2():
t2.speed(0)
i=0
while i < 360:
t2.forward(1)
t2.right(1)
i+=1
t = threading.Thread(target=tes1)
t.daemon = True # thread dies when main thread (only non-daemon thread) exits.
t.start()
t3 = threading.Thread(target=tes2)
t3.daemon = True # thread dies when main thread (only non-daemon thread) exits.
t3.start()
And what is the best suggestion multiprocessing or multithreading?
Upvotes: 5
Views: 9957
Reputation: 1
none of these answers really speed up the drawing, and to prove it i ran a test to compare them:
import turtle
from time import time
import queue
import threading
start = time()
turtle.speed(0)
for i in range(360):
turtle.forward(1)
turtle.left(1)
for i in range(360):
turtle.forward(1)
turtle.right(1)
result1 = time()-start
start = time()
def tes1():
for i in range(360):
graphics.put(turtle1.forward)
graphics.put(turtle1.left)
def tes2():
for i in range(360):
graphics.put(turtle2.forward)
graphics.put(turtle2.right)
graphics = queue.Queue(1) # size = number of hardware threads you have - 1
turtle1 = turtle.Turtle('turtle')
turtle1.speed(0)
thread1 = threading.Thread(target=tes1)
thread1.daemon = True # thread dies when main thread (only non-daemon thread) exits.
thread1.start()
turtle2 = turtle.Turtle('turtle')
turtle2.speed(0)
thread2 = threading.Thread(target=tes2)
thread2.daemon = True # thread dies when main thread (only non-daemon thread) exits.
thread2.start()
while not graphics.empty():
(graphics.get())(1)
resutl2 = time()-start
print(f"time to draw without multithreading: {result1}\n\
time with multithreading: {resutl2}")
and the results are that the multithreading is actually slower, on my machine i got these results:
time to draw without multithreading: 16.973097562789917
time with multithreading: 17.62730455398559
I believe the reason for why multithreading is slower is that we don't multithread the drawing process but only the process of finding the path. we then execute the directions linearly on a single thread that jumps between the turtles as it draws making it seem like they both draw at the same time. The calculation of the path is proably the part that takes longer for the multithreading.
Upvotes: 0
Reputation: 592
I think the coroutines and generators that Beazley preaches (on p. 447) are really more logical here:
Note: the deque
from the collections
module is more reliable too.
import turtle
from collections import deque
def move1():
for _ in range(360):
turtle1.forward(1)
turtle1.left(1)
yield
def move2():
for _ in range(360):
turtle2.forward(1)
turtle2.right(1)
yield
# Create turtles
turtle1 = turtle.Turtle('turtle')
turtle1.speed('fastest')
turtle2 = turtle.Turtle('turtle')
turtle2.speed('fastest')
# Create and populate a task queue
taskqueue = deque()
taskqueue.append(move1()) # Add tasks (generators)
taskqueue.append(move2())
while taskqueue: # Run all of the tasks
# Get the next task
task = taskqueue.pop()
try:
# Run it to the next yield and enqueue
next(task)
taskqueue.appendleft(task)
except StopIteration:
# Task is done
pass
turtle.done()
Upvotes: 1
Reputation: 19372
I've created a threaded_turtle
package, which utilises functionality of queue.Queue
to seamlessly execute all turtle instructions in the main thread, while the code is still written as if the turtles were running in different threads.
threaded_turtle
is on GitLab: https://gitlab.com/zvone/threaded_turtle
With that package, the code from the question needs only minor modifications to work:
import turtle
from threaded_turtle import ThreadSerializer, TurtleThread
ctrl = ThreadSerializer() ## <-- create a serializer
t1=turtle.Turtle()
t2=turtle.Turtle()
def tes1(t1): ## <-- additional argument
t1.speed(0)
i=0
while i < 360:
t1.forward(1)
t1.left(1)
i+=1
def tes2(t2): ## <-- additional argument
t2.speed(0)
i=0
while i < 360:
t2.forward(1)
t2.right(1)
i+=1
t = TurtleThread(ctrl, t1, target=tes1) ## <-- additional arguments
t.daemon = True
t.start()
t3 = TurtleThread(ctrl, t2, target=tes2) ## <-- additional arguments
t3.daemon = True
t3.start()
ctrl.run_forever(1) ## <-- run the serializer
The result:
Upvotes: 2
Reputation: 41905
... I want multithreading or multiprocessing answer and I'm insisting on it.
The turtle module can be used with threading if we carefully walk a tightrope where only the main thread issues turtle commands:
import queue
import threading
import turtle
def tes1():
for _ in range(360):
graphics.put(turtle1.forward)
graphics.put(turtle1.left)
def tes2():
for _ in range(360):
graphics.put(turtle2.forward)
graphics.put(turtle2.right)
def process_queue():
while not graphics.empty():
(graphics.get())(1)
if threading.active_count() > 1:
turtle.ontimer(process_queue, 100)
graphics = queue.Queue(1) # size = number of hardware threads you have - 1
turtle1 = turtle.Turtle('turtle')
turtle1.speed('fastest')
thread1 = threading.Thread(target=tes1)
thread1.daemon = True # thread dies when main thread (only non-daemon thread) exits.
thread1.start()
turtle2 = turtle.Turtle('turtle')
turtle2.speed('fastest')
thread2 = threading.Thread(target=tes2)
thread2.daemon = True # thread dies when main thread (only non-daemon thread) exits.
thread2.start()
process_queue()
turtle.exitonclick()
We're using the queue module for thread-safe communications.
Upvotes: 9
Reputation: 215
The turtle module does not support multithreading. I think the only thing you can do here is what others have already suggested: Make a bunch of turtles. Alternatively, you could use something like mtTkinter, which is exactly like tkinter, but supports threading.
Upvotes: 1
Reputation: 304375
8 turtles at a time is no problem either
import turtle
turtle.delay(0)
t = [turtle.Turtle() for i in range(8)]
for i, j in enumerate(t):
j.right(i*45)
j.speed(0)
for i in range(360):
for j in t:
j.forward(1)
j.right(1)
Upvotes: 1
Reputation: 11846
Is it really necessary that the turtles are in different threads? What about this?
import turtle
t1 = turtle.Turtle()
t2 = turtle.Turtle()
t1.speed(0)
t2.speed(0)
for i in range(360):
t1.forward(1)
t1.left(1)
t2.forward(1)
t2.right(1)
Upvotes: 3