Multithreading With Python Turtle
Asked Answered
H

7

5

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?

Hersey answered 21/10, 2013 at 15:19 Comment(1)
Probably an XY problem. To move multiple turtles simultaneously, either disable tracer or use small incremental movements in a loop. See How to move multiple turtles at the same time?Inexpugnable
S
3

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)
Supernova answered 21/10, 2013 at 15:28 Comment(3)
Your answer is good for two turtles but imagine we have 8 turtle and we want to order them to draw at the same time so we have very low processing speed!Hersey
Perhaps mtTkinter can help you. If you are trying to make super efficient turtles, I don't think you are going down the right pathEadmund
I think that is a bad solution because you can't manage separately the draws of each turtles contrary to the solution given by cdlane.Devilry
U
8

... 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.

enter image description here

Unharness answered 29/6, 2017 at 19:31 Comment(0)
S
3

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)
Supernova answered 21/10, 2013 at 15:28 Comment(3)
Your answer is good for two turtles but imagine we have 8 turtle and we want to order them to draw at the same time so we have very low processing speed!Hersey
Perhaps mtTkinter can help you. If you are trying to make super efficient turtles, I don't think you are going down the right pathEadmund
I think that is a bad solution because you can't manage separately the draws of each turtles contrary to the solution given by cdlane.Devilry
G
2

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:

Screenshot of two turtles circling simultaneously

Gritty answered 4/6, 2018 at 21:25 Comment(2)
How do you install your module, when I go to your repository I don't see a setup.pyReligionism
@Religionism Just put the threaded_turtle package (i.e. directory) somewhere in you pythonpath, e.g. next to your source code.Gritty
E
1

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)
Eadmund answered 21/10, 2013 at 15:36 Comment(1)
It's true but please I want multithreading or multiprocessing answer and I'm insisting on it.Hersey
O
1

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.

Oas answered 27/12, 2013 at 19:46 Comment(0)
C
1

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()
Cohin answered 7/7, 2018 at 22:36 Comment(0)
W
0

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.

Whippet answered 30/10, 2023 at 18:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.