'Print odd and even numbers in two threads

I am learning threads in Python and tried a program to print odd and even numbers in sequence using two separate threads. Below is the program. The program runs correctly but it does not exit. Can someone please point out the mistake I have done?

import threading

odd_event = threading.Event()
even_event = threading.Event()


def printEven(n):
    for i in range(0, n, 2):
        print(i)
        odd_event.set()
        even_event.clear()
        even_event.wait()

def printOdd(n):
    odd_event.wait()
    for i in range(1, n, 2):
        print(i)
        even_event.set()
        odd_event.clear()
        odd_event.wait()

if __name__ == "__main__":
    n = 99
    t1 = threading.Thread(target=printEven, args=(n,))
    t2 = threading.Thread(target=printOdd, args=(n,))

    t1.start()
    t2.start()

    t1.join()
    t2.join()



Solution 1:[1]

Adding on @Roland's response, you could also check for when the event isn't supposed to wait so you don't call the wait function when you're not supposed to. For example, with n=99 - though you can adapt the approach for whichever use case you want - the printEven could look like:

def printEven(n):
    for i in range(0, n, 2):
        print(i)
        odd_event.set()
        even_event.clear()
        if i != n-1:
            even_event.wait()
        else: # here, we know there won't be any even number to print, so we don't need to wait
            print("don't wait")

Solution 2:[2]

The problem

When I changed your code slightly:

def printEven(n):
    for i in range(0, n, 2):
        print(i)
        odd_event.set()
        even_event.clear()
        print("waiting for even event")
        even_event.wait()
    print("printEven finished")

def printOdd(n):
    odd_event.wait()
    for i in range(1, n, 2):
        print(i)
        even_event.set()
        odd_event.clear()
        print("waiting for odd event")
        odd_event.wait()
    print("printOdd finished")

This is what I saw:

95
waiting for odd event
96
waiting for even event
97
waiting for odd event
98
waiting for even event
printOdd finished

What happens is that printEven is waiting for an event that will never happen, because printOdd has already exited.

Solution

Add a timeout to the wait in the for-loops:

import threading

odd_event = threading.Event()
even_event = threading.Event()
TIMEOUT = 0.1


def printEven(n):
    for i in range(0, n, 2):
        print(i, flush=True)
        odd_event.set()
        even_event.clear()
        # print("waiting for even event", flush=True)
        even_event.wait(timeout=TIMEOUT)
    # print("printEven finished", flush=True)


def printOdd(n):
    odd_event.wait()
    for i in range(1, n, 2):
        print(i, flush=True)
        even_event.set()
        odd_event.clear()
        # print("waiting for odd event", flush=True)
        odd_event.wait(timeout=TIMEOUT)
    # print("printOdd finished", flush=True)


if __name__ == "__main__":
    n = 99
    t1 = threading.Thread(target=printEven, args=(n,), daemon=False)
    t2 = threading.Thread(target=printOdd, args=(n,), daemon=False)

    t1.start()
    t2.start()

Of note:

  • Added flush=True to print calls to ensure output buffers are flushed.
  • Removed the join calls, they are not needed in this case. The main thread is a non-daemon thread, and the new threads inherit this property. According to the documentation:

The entire Python program exits when no alive non-daemon threads are left.

So when the print threads have exited, the main thread will do so as well.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Emmanuel Murairi
Solution 2