Skip to content

Threading

from time import sleep
from threading import Thread, Condition
from queue import Queue
import random


# Threading
class Producer:
    def __init__(self):
        self.products = []
        self.productsAvail = False

    # function to produce
    def produce(self):
        for i in range(1, 5):
            self.products.append("Products" + str(i))
            print("Product added!")
            sleep(1)
        self.productsAvail = True


class Consumer:
    def __init__(self):
        self.producer = producer

    def consume(self):
        while not self.producer.productsAvail:
            print("waiting...")
            sleep(0.2)
        print("Products added ", self.producer.products)


# 2 ways to create a thread:
# using the Thread() constructor and passing functions along with the arguments that the function uses.
#   The created thread object is started using start() method.
# Extending the Thread class and overloading the run method and then the thread object is started using the start method
# Create 2 threads
producer = Producer()
consumer = Consumer()
p = Thread(target=producer.produce())
c = Thread(target=consumer.consume())
p.start()
c.start()


"""
Wait and Notify
Assign a Condition object (lock 🔒) to the producer.
In the producer's produce method, call acquire() on lock, do any producing work. Once the work is done, call notify() to
    notify the waiting thread and call release.
In the consumer thread's consume method, call acquire() on the lock of the producer object that is avaialble to the
    consumer object. And then call wait() with a timeout value passed.
Finally call release method() on the lock object in the consume.
"""


class Producer:
    # constructor
    def __init__(self) -> None:
        self.products = []
        self.c = Condition()
        # function to produce

    def produce(self):
        # acquire the lock
        self.c.acquire()
        for i in range(1, 5):
            self.products.append("Products" + str(i))
            print("product added!")
            sleep(1)
        # notify all the waiting threads
        self.c.notify()
        # release the lock
        self.c.release()


# defining Consumer
class Consumer:
    # constructor
    def __init__(self, producer):
        self.producer = producer

    # consume
    def consume(self):
        # acquire the lock of the producer
        self.producer.c.acquire()
        # now wait for the producer to call notify
        self.producer.c.wait(timeout=0)
        self.producer.c.release()
        print("products added: ", self.producer.products)


# create the two threads
producer = Producer()
consumer = Consumer(producer)
p = Thread(target=producer.produce)
c = Thread(target=consumer.consume)
p.start()
c.start()

"""
Queue and Thread communication
Queue class has two methods get and put. Both lock the queue while one is executing.
Hence, synchronization is taken care of.
Producer puts work done on a queue. Same queue is shared with consumer. Which then calls get() to get the data.
"""


# create a producer method
# that accepts a queue as parameter
def producer(q):
    # run an infinite loop that produces data
    while True:
        print("producing...")
        q.put(random.randint(1, 30))
        sleep(3)


# create a consumer method
def consumer(q):
    # keep asking for data
    while True:
        print("ready and waiting...")
        print("consume data: ", q.get())
        sleep(3)


# create a queue
q = Queue()
# create two threads
p = Thread(target=producer, args=(q,))
c = Thread(target=consumer, args=(q,))
p.start()
c.start()