'Python How to make function run simultaneously using asyncio

I am currently new to Python and wanted to create a system that takes 10 customers that come in at an interval of 1 - 25 seconds. They are sent to a queue, and the customers in front of the queue will be sent to one of the four open counters. The counter will service them for 1-100 seconds and record each customer's start time and end time of service. I wanted to run the customer entering and the counter service simultaneously using asyncio but am unable to make them run simultaneously as they run in order. My question is, how can I make my code able to run these two functions simultaneously? My code:

import asyncio
from random import randint
import collections
import time
#create counter true false
Counter_List = {0: True, 1: True, 2: True, 3: True}
#hash table 
served = collections.defaultdict(list)
#empty que list
que_list = collections.deque([])
#global variable
total_customer = 0
counter = 0
start_time = time.time()
#event loop
#interarrival time
async def interarrival_time():
    global total_customer
    while total_customer < 10:
        print('customer ' + str(total_customer) + ' entered')
        time.sleep(randint(1,25))
        #que to right side
        que_list.append(total_customer)
        total_customer += 1
       
#service time          
#start service
async def service_start():
    global counter, total_customer, start_time
    while counter <= 4 and len(que_list) > 0:
        for x in Counter_List:
            if Counter_List[x] == True:
                #set to false the current counter
                Counter_List[x] = False
                counter += 1
                print('counter ' + str(x) + ' available')
                #que list
                customerid = que_list[0]
                #pop left of the quelist
                que_list.popleft()
                #get customer in que, record counter, start time
                served[x].append('customer ' + str(customerid))
                served[x].append('Start time: ' + str(int(time.time() - start_time)))
                await asyncio.sleep(randint(1,100))
                #record end time
                served[x].append('End time: ' + str(int(time.time() - start_time)))
                #set counter to true
                Counter_List[x] = True
                counter -= 1
                if total_customer == 10 and len(que_list) == 0:
                    #print the dictionary
                    print (served)
                    loop.stop()
            
loop = asyncio.get_event_loop()

try:
    asyncio.ensure_future(interarrival_time())
    asyncio.ensure_future(service_start())
    loop.run_forever()
finally:
    loop.close()

My output:

customer 0 entered
customer 1 entered
customer 2 entered
customer 3 entered
customer 4 entered
customer 5 entered
customer 6 entered
customer 7 entered
customer 8 entered
customer 9 entered
counter 0 available
counter 1 available
counter 2 available
counter 3 available
counter 0 available
counter 1 available
counter 2 available
counter 3 available
counter 0 available
counter 1 available
defaultdict(<class 'list'>, {0: ['customer 0', 'Start time: 134', 'End time: 183', 'customer 4', 'Start time: 327', 'End time: 421', 'customer 8', 'Start time: 562', 'End time: 651'], 1: ['customer 1', 'Start time: 183', 'End time: 201', 'customer 5', 'Start time: 421', 'End time: 454', 'customer 9', 'Start time: 651', 'End time: 656'], 2: ['customer 2', 'Start time: 201', 'End time: 267', 'customer 6', 'Start time: 454', 'End time: 538'], 3: ['customer 3', 'Start time: 267', 'End time: 327', 'customer 7', 'Start time: 538', 'End time: 562']})

The 'customer x entered' and 'counter x available' are ways to keep track of if the system is running simultaneously. I would like it so that it will be as an example:

customer 1 entered
counter 0 available
customer 2 entered
counter 1 available
customer 3 entered
counter 2 available
customer 4 entered
counter 0 available
customer 5 entered
counter 3 available
customer 6 entered
customer 7 entered
counter 2 available 
and so on


Solution 1:[1]

What you want is asyncio.Queue for consumer-producer types of flows. Whenever a counter is free, it simply needs to ask the queue for the next customer.

import asyncio
import random

async def counter_task(customers: asyncio.Queue, counter_id: int) -> None:
    while True:
        # Get the next available customer.
        customer = await customers.get()
        print(f"Customer {customer} is being serviced at counter {counter_id}.")
        # Work on that customer.
        await asyncio.sleep(random.randint(1, 100))
        print(f"Customer {customer} has left counter {counter_id}.")
        # Signal the customer is done.
        customers.task_done()

async def main() -> None:
    customers = asyncio.Queue()
    counters = [asyncio.create_task(counter_task(customers, i)) for i in range(4)]
    try:
        # Create 10 customers.
        for customer in range(10):
            print(f"Customer {customer} has entered.")
            await customers.put(customer)
            await asyncio.sleep(random.randint(1, 25))
        # Wait for them to be signalled as finished.
        await customers.join()
    finally:
        # Stop the counters.
        for counter in counters:
            counter.cancel()
        for counter in counters:
            try:
                await counter
            except asyncio.CancelledError:
                pass

asyncio.run(main())

Sample output:

Customer 0 has entered.
Customer 0 is being serviced at counter 0.
Customer 1 has entered.
Customer 1 is being serviced at counter 1.
Customer 2 has entered.
Customer 2 is being serviced at counter 2.
Customer 1 has left counter 1.
Customer 3 has entered.
Customer 3 is being serviced at counter 3.
Customer 4 has entered.
Customer 4 is being serviced at counter 1.
Customer 5 has entered.
Customer 0 has left counter 0.
Customer 5 is being serviced at counter 0.
Customer 6 has entered.
Customer 7 has entered.
Customer 4 has left counter 1.
Customer 6 is being serviced at counter 1.
Customer 8 has entered.
Customer 2 has left counter 2.
Customer 7 is being serviced at counter 2.
Customer 9 has entered.
Customer 3 has left counter 3.
Customer 8 is being serviced at counter 3.
Customer 7 has left counter 2.
Customer 9 is being serviced at counter 2.
Customer 5 has left counter 0.
Customer 6 has left counter 1.
Customer 8 has left counter 3.
Customer 9 has left counter 2.

Solution 2:[2]

If you don't care about overhead and memory in your app(it is not heavy) use Multi Threading. Although asyncio has the advantage of less overhead, but the codes which you use to write an asyncio program is very specific and it is very hard to use them in another app. For instance you can see in API frameworks, the developers develop the framework in 2 ways, ASGI(asyncio) and WSGI(not async)!!!!

For example: https://falcon.readthedocs.io/en/stable/user/index.html

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 Simply Beautiful Art
Solution 2 Sina Karimi