user20210310
user20210310

Reputation: 112

Multithreading does not execute properly

I am trying to run two functions(tasks) sequentially using multithreading. The problem is when I am implementing it executes one function completely then steps into the another one. What I expected is to step over to another function while loop completes its first run in the given function.

As a side note, I set time.sleep() to simulate the receiving and preparation scenario of some order.

from collections import deque
import time
from threading import Thread


class Order_queue:
    
    def __init__(self):
        self.buffer = deque()
    
    def place_order(self, basket):
        for item in basket:
            self.buffer.appendleft(item)
            print(f"Ordered: {item}")
            time.sleep(3)

        
    def serve_order(self):
        time.sleep(1)
        while not self.is_empty():
            served = self.buffer.pop()
            time.sleep(2)
            print(f"Served: {served}")


    def is_empty(self):
        return len(self.buffer)==0
    

order_list = ['pizza','samosa','pasta','biryani','burger']
order = Order_queue()


th1 = Thread(target=order.place_order, args=(order_list,))
th2 = Thread(target=order.serve_order)

th1.start()
th2.start()

th1.join()
th2.join()

Output:

Ordered: pizza
Ordered: samosa
Ordered: pasta
Ordered: biryani
Ordered: burger
Served: pizza
Served: samosa
Served: pasta
Served: biryani
Served: burger

Expected:

Ordered: pizza
Served: pizza
Ordered: samosa
Served: samosa
....

Upvotes: 0

Views: 91

Answers (2)

martineau
martineau

Reputation: 123453

You don't need threads to run two functions (tasks) sequentially because it can be done in by using coroutines (a form of non-preemptive multitasking), which are relatively simple to create in Python because all it requires is using a yield expression inside a function or method.

Similarly, you don't need a deque or some other buffer for the tasks since there's never more than one being processed (i.e. ordered and served) at a time.

Here's how to implement things using the approach I am suggesting. Note that the two functions involved really don't need to be part of a class, but I put them in one anyway to make things a little more like the code in your question.

import time


def coroutine(func):
    """ Decorator to make coroutines automatically start when called. """
    def start(*args, **kwargs):
        cr = func(*args, **kwargs)
        next(cr)
        return cr
    return start


class OrderQueue:

    @coroutine
    def serve_orders(self):
        while True:
            served = (yield)
            print(f'Served: {served}')
            time.sleep(2)

    def place_orders(self, basket):
        server = self.serve_orders()  # Initialize coroutine generator method.
        for item in basket:
            print(f'Ordering: {item}')
            server.send(item)
            time.sleep(0.5)
        server.close()  # Causes coroutine generator method to exit.


order_list = ['pizza', 'samosa', 'pasta', 'biryani', 'burger']

order_queue = OrderQueue()
order_queue.place_orders(order_list)

print('-- Fini --')

Output:

Ordering: pizza
Served: pizza
Ordering: samosa
Served: samosa
Ordering: pasta
Served: pasta
Ordering: biryani
Served: biryani
Ordering: burger
Served: burger
-- Fini --

Upvotes: 1

Hoxha Alban
Hoxha Alban

Reputation: 1137

The code is working correctly, as @m_dubya said there is a total of 3s sleep between the append in place_order and the pop in serve_order.

For example if you change the sleep in the place_order function to 2 seconds the result will be:

Ordered: pizza
Ordered: samosa
Served: pizza
Ordered: pasta
Served: samosa
Ordered: biryani
Served: pasta
Ordered: burger
Served: biryani
Served: burger

Upvotes: 0

Related Questions