hamidfzm
hamidfzm

Reputation: 4695

Multithreading With Python Turtle

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

Answers (7)

Vincent Kleis
Vincent Kleis

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

Alex Fedotov
Alex Fedotov

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

zvone
zvone

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:

Screenshot of two turtles circling simultaneously

Upvotes: 2

cdlane
cdlane

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.

enter image description here

Upvotes: 9

recursive recursion
recursive recursion

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

John La Rooy
John La Rooy

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

ford
ford

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

Related Questions