Nearby lessons

145 of 159

Python - Inter Thread Communication

📌 What You Will Learn
  • Explain why threads need communication
  • Use the Event methods set(), clear(), is_set() and wait()
  • Understand the Condition object and its acquire/wait/notify methods
  • Use a Queue for producer-consumer synchronization
  • Compare Event, Condition and Queue

What is Inter Thread Communication?

Sometimes, as part of a programming requirement, multiple Threads need to communicate with each other.

This communication between Threads is called:

Inter Thread Communication

Simple Definition:

Inter Thread Communication is the mechanism through which one Thread communicates or sends a notification to another Thread.

For example, one Thread may perform some work and another Thread may have to wait until that work is completed.

Producer and Consumer Example

A common example of Inter Thread Communication is the:

Producer - Consumer

Suppose our application contains two Threads:

  • Producer Thread
  • Consumer Thread

The Producer Thread creates new items.

After producing an item, it has to inform the Consumer Thread that an item is available.

Only after receiving this notification can the Consumer Thread consume the item.

Producer Thread
      │
      ▼
Produce Item
      │
      ▼
Send Notification
      │
      ▼
Consumer Thread
      │
      ▼
Receive Notification
      │
      ▼
Consume Item

This communication between Producer and Consumer is called Inter Thread Communication.

Why Do Threads Need Communication?

Consider a Consumer Thread that needs some data.

If the Producer has not produced the data yet, the Consumer should not continue immediately.

Consumer
   │
   ▼
Need Item
   │
   ▼
Item Available?
   │
 ┌─┴─┐
No   Yes
│     │
▼     ▼
Wait  Consume

The Producer should notify the Consumer when the required data becomes available.

Therefore, Threads need a proper communication mechanism.

Ways to Implement Inter Thread Communication

In Python, Inter Thread Communication can be implemented using:

  1. Event
  2. Condition
  3. Queue
  4. etc.
Mechanism Purpose
Event Simple notification between Threads
Condition More advanced waiting and notification mechanism
Queue Advanced mechanism for communication and sharing data

Inter Thread Communication Using Event Object

The Event object is the simplest communication mechanism between Threads.

The basic idea is:

  • One Thread signals an Event.
  • Other Threads wait for that Event.
Thread-1
Producer
   │
   ▼
event.set()
   │
   │ Notification
   ▼
Event Object
   │
   ▼
Thread-2
Consumer
   │
   ▼
Continues

The Event object acts like a signal shared between Threads.

Creating an Event Object

We can create an Event object as follows:

import threading

event = threading.Event()

If everything is imported from the threading module:

from threading import *

event = Event()

Here:

  • Event is provided by the threading module.
  • event refers to the Event object.

Working of Event Object

An Event object internally maintains a flag.

This internal flag can be:

  • Set
  • Cleared

Conceptually:

Event Object
     │
     ▼
Internal Flag
     │
 ┌───┴────┐
 ▼        ▼
False    True
Cleared  Set

Threads can wait until the Event becomes set.

When another Thread sets the Event, waiting Threads are allowed to continue.

Initial State of Event

When an Event object is created, its internal flag is initially in the cleared state.

event = Event()

Internal Flag
     │
     ▼
   False

Therefore, a Thread calling:

event.wait()

waits until another Thread sets the Event.

Methods of Event Class

Method Purpose
set() Sets the internal flag
clear() Clears the internal flag
is_set() Checks whether the Event is set
wait() Waits until the Event becomes set

1. set() Method

The set() method is used as follows:

event.set()

When set() is called:

  • The internal flag becomes True.
  • It acts like a GREEN Signal for waiting Threads.
Before set()

Flag = False
     │
     ▼
event.set()
     │
     ▼
Flag = True
     │
     ▼
GREEN SIGNAL
     │
     ▼
Waiting Threads
Can Continue

2. clear() Method

The clear() method is used as follows:

event.clear()

When clear() is called:

  • The internal flag becomes False.
  • It acts like a RED Signal.
Before clear()

Flag = True
     │
     ▼
event.clear()
     │
     ▼
Flag = False
     │
     ▼
RED SIGNAL

3. is_set() Method

The is_set() method checks whether the Event is currently set.

event.is_set()

Conceptually:

Flag = True
   │
   ▼
event.is_set()
   │
   ▼
True

If the Event is cleared:

Flag = False
   │
   ▼
event.is_set()
   │
   ▼
False

This method is especially useful when a Thread needs to continue working only while the Event remains set.

4. wait() Method

The wait() method is used as follows:

event.wait()

We can also specify a waiting time:

event.wait(seconds)

A Thread calling wait() waits until the Event becomes set.

Thread
  │
  ▼
event.wait()
  │
  ▼
Is Event Set?
  │
 ┌┴─────┐
No      Yes
│        │
▼        ▼
Wait   Continue

GREEN Signal and RED Signal Concept

Operation Flag Meaning
event.set() True GREEN Signal
event.clear() False RED Signal
event.set()
     │
     ▼
GREEN
     │
     ▼
Threads Continue


event.clear()
     │
     ▼
RED
     │
     ▼
Threads Wait

Pseudo Code

🐍Code Cell
1event = threading.Event()
2 
3# Consumer thread waits
4event.wait()
5 
6# Producer thread sets or clears the event
7event.set()
8event.clear()
Output
No output captured.

Understanding the Pseudo Code

The Consumer Thread waits using:

event.wait()

The Producer Thread can give a notification using:

event.set()

It can clear the Event using:

event.clear()

Basic flow:

Consumer
   │
   ▼
wait()
   │
   ▼
Waiting
   │
   │
Producer
   │
   ▼
set()
   │
   ▼
Consumer Continues

Demo Program 1 - Producer and Consumer

🐍Code Cell
1from threading import *
2import time
3 
4def producer():
5 time.sleep(5)
6 print("Producer thread producing items:")
7 print("Producer thread giving notification by setting event")
8 event.set()
9 
10def consumer():
11 print("Consumer thread is waiting for updation")
12 event.wait()
13 print("Consumer thread got notification and consuming items")
14 
15event = Event()
16 
17t1 = Thread(target=producer)
18t2 = Thread(target=consumer)
19 
20t1.start()
21t2.start()
Output
No output captured.

Output

Program Explanation - Producer and Consumer

Step 1: Import Required Modules

from threading import *
import time

The threading functionality and time module are imported.


Step 2: Define producer()

def producer():

This function represents the Producer Thread.


Step 3: Producer Sleeps

time.sleep(5)

The Producer waits for five seconds before producing the items.


Step 4: Produce Items

print("Producer thread producing items:")

After five seconds, the Producer starts producing items.


Step 5: Give Notification

event.set()

The Producer sets the Event.

The internal Event flag becomes:

True

This notification allows the waiting Consumer Thread to continue.


Step 6: Define consumer()

def consumer():

This function represents the Consumer Thread.


Step 7: Consumer Displays Waiting Message

print("Consumer thread is waiting for updation")

The Consumer informs us that it is waiting for an update.


Step 8: Consumer Calls wait()

event.wait()

Because the Event has not yet been set, the Consumer enters the waiting state.


Step 9: Consumer Gets Notification

When the Producer executes:

event.set()

the waiting Consumer becomes eligible to continue.


Step 10: Consumer Consumes Items

print("Consumer thread got notification and consuming items")

The Consumer receives the notification and continues its work.

Execution Flow - Producer and Consumer

Program Starts
      │
      ▼
Create Event Object
      │
      ▼
Create Producer Thread
and Consumer Thread
      │
      ▼
Start Both Threads
      │
      ▼
Consumer Starts
      │
      ▼
event.wait()
      │
      ▼
Consumer Enters
Waiting State
      │
      ▼
Producer Sleeps
5 Seconds
      │
      ▼
Producer Produces
Items
      │
      ▼
Producer Calls
event.set()
      │
      ▼
Event Flag = True
      │
      ▼
Waiting Consumer
Gets Notification
      │
      ▼
Consumer Continues
      │
      ▼
Consumes Items
      │
      ▼
Program Completes

Important Point in Producer-Consumer Program

The important relationship is:

Consumer
   │
   ▼
event.wait()


Producer
   │
   ▼
event.set()

The Consumer is the Thread waiting for an update.

The Producer performs the update and sends the notification.

Therefore:

Producer → set()

Consumer → wait()

Demo Program 2 - Traffic Signal Example

🐍Code Cell
1from threading import *
2import time
3 
4def trafficpolice():
5 while True:
6 time.sleep(10)
7 print("Traffic Police Giving GREEN Signal")
8 event.set()
9 
10 time.sleep(20)
11 print("Traffic Police Giving RED Signal")
12 event.clear()
13 
14def driver():
15 num = 0
16 
17 while True:
18 print("Drivers waiting for GREEN Signal")
19 event.wait()
20 
21 print("Traffic Signal is GREEN. Vehicles can move")
22 
23 while event.is_set():
24 num = num + 1
25 print("Vehicle No:", num, "Crossing the Signal")
26 time.sleep(2)
27 
28 print("Traffic Signal is RED. Drivers have to wait")
29 
30event = Event()
31 
32t1 = Thread(target=trafficpolice)
33t2 = Thread(target=driver)
34 
35t1.start()
36t2.start()
Output
No output captured.

Traffic Signal Program - Sample Output

The program runs continuously, so there is no single final output.

A sample pattern is:

Drivers waiting for GREEN Signal
Traffic Police Giving GREEN Signal
Traffic Signal is GREEN. Vehicles can move
Vehicle No: 1 Crossing the Signal
Vehicle No: 2 Crossing the Signal
Vehicle No: 3 Crossing the Signal
...
Traffic Police Giving RED Signal
Traffic Signal is RED. Drivers have to wait
Drivers waiting for GREEN Signal
...

The exact number of vehicles printed during each GREEN period depends on Thread scheduling and timing.

Traffic Signal Program Explanation

This program uses an Event object like a real traffic signal.

There are two Threads:

Thread Responsibility
Traffic Police Thread Changes signal between GREEN and RED
Driver Thread Waits for GREEN and allows vehicles to cross

Traffic Police Thread

The Traffic Police executes:

def trafficpolice():
    while True:

It works continuously.

First it waits for 10 seconds:

time.sleep(10)

Then it gives a GREEN signal:

print("Traffic Police Giving GREEN Signal")
event.set()

The Event flag becomes:

True

After that, the Traffic Police waits for 20 seconds:

time.sleep(20)

Then it gives a RED signal:

print("Traffic Police Giving RED Signal")
event.clear()

The Event flag becomes:

False

Traffic Police Signal Flow

Traffic Police
      │
      ▼
Wait 10 Seconds
      │
      ▼
event.set()
      │
      ▼
GREEN SIGNAL
Flag = True
      │
      ▼
Wait 20 Seconds
      │
      ▼
event.clear()
      │
      ▼
RED SIGNAL
Flag = False
      │
      ▼
Repeat

Driver Thread

The Driver Thread starts with:

num = 0

This variable is used to generate vehicle numbers.

The Driver continuously executes:

while True:

First, drivers wait for the GREEN signal:

print("Drivers waiting for GREEN Signal")
event.wait()

If the Event flag is False, the Driver Thread waits.

When the Traffic Police calls:

event.set()

the Event becomes set and the Driver continues.

Vehicles Crossing During GREEN Signal

After receiving the GREEN signal:

print("Traffic Signal is GREEN. Vehicles can move")

The Driver checks:

while event.is_set():

As long as the Event remains set, vehicles continue crossing.

num = num + 1

print("Vehicle No:", num, "Crossing the Signal")

time.sleep(2)

Every two seconds, another vehicle is shown crossing the signal.

Event Set?
   │
   ▼
 True
   │
   ▼
Vehicle Crosses
   │
   ▼
Wait 2 Seconds
   │
   └──────────────┐
                  │
                  ▼
             Check Again

What Happens When Signal Becomes RED?

The Traffic Police eventually executes:

event.clear()

The Event flag becomes:

False

Therefore:

while event.is_set():

becomes False.

The Driver exits the inner loop and executes:

print("Traffic Signal is RED. Drivers have to wait")

Then the outer loop repeats and the Driver again reaches:

event.wait()

Now it waits for the next GREEN signal.

Complete Traffic Signal Execution Flow

Program Starts
      │
      ▼
Create Event
      │
      ▼
Create Traffic Police
and Driver Threads
      │
      ▼
Start Both Threads
      │
      ▼
Driver Calls
event.wait()
      │
      ▼
Driver Waits
      │
      ▼
Traffic Police
Waits 10 Seconds
      │
      ▼
event.set()
      │
      ▼
GREEN Signal
      │
      ▼
Driver Continues
      │
      ▼
Vehicles Cross
      │
      ▼
event.is_set()
Returns True
      │
      ▼
Traffic Police
Waits 20 Seconds
      │
      ▼
event.clear()
      │
      ▼
RED Signal
      │
      ▼
event.is_set()
Returns False
      │
      ▼
Vehicles Stop
      │
      ▼
Driver Calls
event.wait() Again
      │
      ▼
Process Repeats

Event Flag State Diagram

             Event Object
                  │
                  ▼
             Flag = False
                  │
                  │ event.set()
                  ▼
             Flag = True
                  │
                  │ event.clear()
                  ▼
             Flag = False
Flag Signal Effect
False RED Threads calling wait() remain waiting
True GREEN Waiting Threads can continue

set(), clear(), is_set() and wait() Comparison

Method Purpose Effect
set() Set Event Flag becomes True
clear() Clear Event Flag becomes False
is_set() Check Event Returns whether Event is set
wait() Wait for Event Blocks until Event is set
wait(seconds) Wait with timeout Waits for Event or specified timeout

Producer-Consumer vs Traffic Signal Example

Concept Producer-Consumer Traffic Signal
Notifier Producer Traffic Police
Waiting Thread Consumer Driver
set() Items available GREEN Signal
clear() Can clear notification state RED Signal
wait() Consumer waits for update Driver waits for GREEN
is_set() Check Event state Check whether signal is still GREEN

Summary

  • Multiple Threads sometimes need to communicate with each other.
  • This communication is called Inter Thread Communication.
  • The Producer–Consumer problem is a common example.
  • The Producer creates an item and notifies the Consumer.
  • The Consumer waits for notification before consuming the item.
  • Python provides several Inter Thread Communication mechanisms:
Event
Condition
Queue
  • The Event object is the simplest communication mechanism.
  • An Event internally maintains a flag.
  • set() makes the flag True.
  • set() acts like a GREEN signal.
  • clear() makes the flag False.
  • clear() acts like a RED signal.
  • is_set() checks whether the Event is currently set.
  • wait() makes a Thread wait until the Event becomes set.
  • wait(seconds) can be used with a waiting time.
  • In the Producer–Consumer example, the Consumer waits and the Producer sets the Event.
  • In the Traffic Signal example, Traffic Police controls the Event and Drivers react to its state.

Important Notes

  1. Inter Thread Communication allows Threads to coordinate with each other.
  2. Producer and Consumer is the basic example used to understand Thread communication.
  3. Python supports Event, Condition, Queue, etc. for Inter Thread Communication.
  4. Event is the simplest mechanism among the mechanisms introduced here.
  5. An Event object maintains an internal flag.
  6. event.set() changes the flag to True.
  7. event.clear() changes the flag to False.
  8. event.set() works like a GREEN signal.
  9. event.clear() works like a RED signal.
  10. event.is_set() checks whether the Event is set.
  11. event.wait() blocks the Thread until the Event is set.
  12. event.wait(seconds) supports waiting with a timeout.
  13. In the Producer–Consumer example, the Consumer calls wait().
  14. The Producer calls set() after producing the items.
  15. In the Traffic Signal example, Traffic Police calls set() for GREEN.
  16. Traffic Police calls clear() for RED.
  17. Drivers use wait() to wait for GREEN.
  18. Drivers use is_set() to continue crossing while the signal remains GREEN.

What is a Condition Object?

A Condition object is a more advanced mechanism than an Event object for Inter Thread Communication.

A Condition represents a state change in an application.

Examples:

  • Producing an item
  • Consuming an item

Threads can wait for a particular condition.

When that condition occurs, another Thread can notify the waiting Threads.

Simple Definition:

A Condition object allows one or more Threads to wait until they are notified by another Thread.

Basic Condition Concept

Consider two Threads:

  • Producer Thread
  • Consumer Thread

The Consumer needs an item from the Producer.

If the item is not available, the Consumer should wait.

After producing the item, the Producer sends a notification.

Consumer Thread
      │
      ▼
Wait for Item
      │
      ▼
condition.wait()
      │
      ▼
   Waiting
      │
      │
      │ Notification
      │
Producer Thread
      │
      ▼
Produce Item
      │
      ▼
condition.notify()
      │
      ▼
Consumer Continues
      │
      ▼
Consume Item

Condition and Lock

A Condition object is always associated with a Lock.

The document describes this as a Reentrant Lock.

The Condition object internally uses this Lock for synchronization.

Condition Object
       │
       ▼
Internal Lock
(Reentrant Lock)
       │
       ├── acquire()
       │
       └── release()

Therefore, a Thread should normally acquire the Condition before performing operations such as:

  • Producing an item
  • Consuming an item
  • Calling wait()
  • Calling notify()

Creating a Condition Object

We can create a Condition object as follows:

import threading

condition = threading.Condition()

If everything is imported from the threading module:

from threading import *

condition = Condition()

Here:

  • Condition is the predefined class.
  • condition is the reference variable.

Methods of Condition Object

Method Purpose
acquire() Acquires the internal Lock
release() Releases the internal Lock
wait() Waits until notification
notify() Notifies one waiting Thread
notify_all() Notifies all waiting Threads

1. acquire() Method

The acquire() method is used as follows:

condition.acquire()

Purpose:

  • Acquire the Condition object before producing or consuming items.
  • The Thread acquires the internal Lock associated with the Condition.
Thread
  │
  ▼
condition.acquire()
  │
  ▼
Acquire Internal Lock
  │
  ▼
Enter Protected Work

2. release() Method

The release() method is used as follows:

condition.release()

Purpose:

  • Release the Condition after producing or consuming an item.
  • The Thread releases the internal Lock.
Protected Work
      │
      ▼
condition.release()
      │
      ▼
Internal Lock Released

3. wait() Method

The wait() method is used as follows:

condition.wait()

We can also specify a waiting time:

condition.wait(time)

The Thread waits until:

  • Another Thread sends a notification, or
  • The specified waiting time expires.
condition.wait()
       │
       ▼
Thread Waits
       │
       ▼
Notification Received
       │
       ▼
Thread Continues

4. notify() Method

The notify() method is used as follows:

condition.notify()

Its purpose is to notify one waiting Thread.

Waiting Threads

T1   T2   T3
│    │    │
└────┼────┘
     │
     ▼
condition.notify()
     │
     ▼
One Waiting Thread
Gets Notification

5. notify_all() Method

The notify_all() method is used as follows:

condition.notify_all()

Its purpose is to notify all waiting Threads.

Waiting Threads

T1   T2   T3
│    │    │
└────┼────┘
     │
     ▼
condition.notify_all()
     │
     ▼
Notify All
Waiting Threads

notify() vs notify_all()

notify() notify_all()
Notifies one waiting Thread. Notifies all waiting Threads.
Useful when only one waiting Thread needs to continue. Useful when all waiting Threads need notification.

Case Study - Producer Thread

The Producer Thread should perform the following operations:

  1. Produce an item.
  2. Acquire the Condition.
  3. Add the item to the shared resource.
  4. Notify waiting Consumers.
  5. Release the Condition.
Producer
   │
   ▼
Generate Item
   │
   ▼
Acquire Condition
   │
   ▼
Add Item
   │
   ▼
Notify Consumer
   │
   ▼
Release Condition

Producer Thread - Pseudo Code

🐍Code Cell
1# Producer Thread
2 
3generate item
4 
5condition.acquire()
6 
7add item to the resource
8 
9condition.notify() # or notify_all()
10 
11condition.release()
Output
No output captured.

Case Study - Consumer Thread

The Consumer Thread should:

  1. Acquire the Condition.
  2. Wait for notification.
  3. Consume the item.
  4. Release the Condition.
Consumer
   │
   ▼
Acquire Condition
   │
   ▼
Wait
   │
   ▼
Receive Notification
   │
   ▼
Consume Item
   │
   ▼
Release Condition

Consumer Thread - Pseudo Code

🐍Code Cell
1# Consumer Thread
2 
3condition.acquire()
4 
5condition.wait()
6 
7consume item
8 
9condition.release()
Output
No output captured.

Producer vs Consumer Responsibilities

Producer Consumer
Produces item Consumes item
Acquires Condition Acquires Condition
Updates shared resource Waits for update
Calls notify() or notify_all() Calls wait()
Releases Condition Releases Condition

Demo Program 1 - Simple Producer and Consumer

Ordering Hazard: If the producer thread runs before the consumer starts waiting, the notification is lost and the program will hang.

Rerun the program, or use a Queue for guaranteed ordering.

🐍Code Cell
1from threading import *
2 
3def consume(c):
4 c.acquire()
5 print("Consumer waiting for updation")
6 c.wait()
7 print("Consumer got notification & consuming the item")
8 c.release()
9 
10def produce(c):
11 c.acquire()
12 print("Producer Producing Items")
13 print("Producer giving Notification")
14 c.notify()
15 c.release()
16 
17c = Condition()
18 
19t1 = Thread(target=consume, args=(c,))
20t2 = Thread(target=produce, args=(c,))
21 
22t1.start()
23t2.start()
Output
No output captured.

Demo Program 1 - Output

Demo Program 1 - Complete Explanation

Step 1: Import threading

from threading import *

This imports Thread and Condition functionality.


Step 2: Define consume()

def consume(c):

The consume() function represents the Consumer Thread.


Step 3: Consumer Acquires Condition

c.acquire()

The Consumer acquires the Condition's internal Lock.


Step 4: Consumer Waits

print("Consumer waiting for updation")
c.wait()

The Consumer now waits until another Thread sends a notification.


Step 5: Define produce()

def produce(c):

This function represents the Producer Thread.


Step 6: Producer Acquires Condition

c.acquire()

The Producer acquires the Condition.


Step 7: Producer Produces Items

print("Producer Producing Items")

The Producer performs its update.


Step 8: Producer Gives Notification

print("Producer giving Notification")
c.notify()

notify() sends a notification to one waiting Thread.

Here, the waiting Consumer receives the notification.


Step 9: Producer Releases Condition

c.release()

The Producer releases the internal Lock.


Step 10: Consumer Continues

print("Consumer got notification & consuming the item")

After receiving the notification, the Consumer continues and consumes the item.


Step 11: Consumer Releases Condition

c.release()

Finally, the Consumer releases the Condition.

Demo Program 1 - Execution Flow

Program Starts
      │
      ▼
Create Condition
      │
      ▼
Create Consumer Thread
and Producer Thread
      │
      ▼
Start Consumer
      │
      ▼
c.acquire()
      │
      ▼
Consumer Calls
c.wait()
      │
      ▼
Consumer Waits
      │
      ▼
Producer Executes
      │
      ▼
c.acquire()
      │
      ▼
Produce Items
      │
      ▼
c.notify()
      │
      ▼
Consumer Gets
Notification
      │
      ▼
Producer Releases
Condition
      │
      ▼
Consumer Continues
      │
      ▼
Consume Item
      │
      ▼
Consumer Releases
Condition

How wait() Works with the Condition Lock

An important concept is that the Consumer initially acquires the Condition:

c.acquire()

and then calls:

c.wait()

While waiting, the Condition allows the internal Lock to become available so another Thread can acquire it and perform the update.

Otherwise, the Producer would never be able to acquire the Condition.

Consumer acquires Condition
          │
          ▼
       c.wait()
          │
          ▼
Wait for Notification
and allow Producer
to use Condition
          │
          ▼
Producer acquires
          │
          ▼
Producer notifies
          │
          ▼
Consumer resumes

Demo Program 2 - Producer and Consumer with Items

Ordering Hazard: If the producer thread runs before the consumer starts waiting, the notification is lost and the program will hang.

Rerun the program, or use a Queue for guaranteed ordering.

🐍Code Cell
1from threading import *
2import time
3import random
4 
5items = []
6 
7def produce(c):
8 while True:
9 c.acquire()
10 
11 item = random.randint(1,100)
12 print("Producer Producing Item:", item)
13 
14 items.append(item)
15 
16 print("Producer giving Notification")
17 c.notify()
18 
19 c.release()
20 time.sleep(5)
21 
22def consume(c):
23 while True:
24 c.acquire()
25 
26 print("Consumer waiting for updation")
27 c.wait()
28 
29 print("Consumer consumed the item", items.pop())
30 
31 c.release()
32 time.sleep(5)
33 
34c = Condition()
35 
36t1 = Thread(target=consume, args=(c,))
37t2 = Thread(target=produce, args=(c,))
38 
39t1.start()
40t2.start()
Output
No output captured.

Demo Program 2 - Sample Output

Demo Program 2 - Program Structure

The second example performs actual data sharing between the Producer and Consumer.

The shared resource is:

items = []

The Producer:

  • Generates a random item.
  • Adds it to items.
  • Sends a notification.

The Consumer:

  • Waits for notification.
  • Removes an item.
  • Consumes it.

Producer Thread - Detailed Explanation

The Producer executes continuously:

while True:

First, it acquires the Condition:

c.acquire()

It generates a random number:

item = random.randint(1,100)

For example:

49

The generated item is displayed:

print("Producer Producing Item:", item)

Then the item is added to the shared list:

items.append(item)

The Producer gives notification:

c.notify()

Finally, it releases the Condition:

c.release()

and waits for five seconds:

time.sleep(5)

Consumer Thread - Detailed Explanation

The Consumer also executes continuously:

while True:

It acquires the Condition:

c.acquire()

Then it displays:

Consumer waiting for updation

and waits:

c.wait()

After receiving notification from the Producer, the Consumer removes an item:

items.pop()

The consumed item is displayed:

print("Consumer consumed the item", items.pop())

Finally:

c.release()
time.sleep(5)

The Consumer releases the Condition and waits for five seconds.

How the Shared List Changes

Suppose the Producer generates:

49

Initially:

items = []

Producer executes:

items.append(49)

Now:

items = [49]

The Producer calls:

c.notify()

The Consumer receives the notification and executes:

items.pop()

Now:

items = []

Conceptually:

Producer
   │
   ▼
Generate 49
   │
   ▼
items.append(49)
   │
   ▼
[49]
   │
   ▼
notify()
   │
   ▼
Consumer
   │
   ▼
items.pop()
   │
   ▼
49 Consumed
   │
   ▼
[]

Demo Program 2 - Execution Flow

Program Starts
      │
      ▼
items = []
      │
      ▼
Create Condition
      │
      ▼
Create Producer and
Consumer Threads
      │
      ▼
Consumer Acquires
Condition
      │
      ▼
Consumer Calls wait()
      │
      ▼
Consumer Waits
      │
      ▼
Producer Acquires
Condition
      │
      ▼
Generate Random Item
      │
      ▼
items.append(item)
      │
      ▼
c.notify()
      │
      ▼
Release Condition
      │
      ▼
Consumer Receives
Notification
      │
      ▼
items.pop()
      │
      ▼
Consume Item
      │
      ▼
Release Condition
      │
      ▼
Sleep
      │
      ▼
Process Repeats

Important Note - Who Calls wait() and notify()?

The document gives an important rule for understanding the Producer–Consumer program.

The Consumer Thread is waiting for an update.

Therefore, the Consumer is responsible for calling:

wait()

The Producer Thread performs the update.

Therefore, the Producer is responsible for calling:

notify()

or:

notify_all()

on the Condition object.

Consumer
   │
   ▼
wait()


Producer
   │
   ├── notify()
   │
   └── notify_all()

Condition vs Event

Event Condition
Simpler communication mechanism More advanced communication mechanism
Internally maintains a flag Associated with a Lock
Uses set() Uses notify()
Uses clear() Uses Lock-based synchronization
Uses wait() Uses wait()
Useful for signal-style communication Useful for waiting for application state changes

Condition Method Comparison

Method Executed By Purpose
acquire() Producer / Consumer Acquire internal Lock
release() Producer / Consumer Release internal Lock
wait() Usually Consumer Wait for an update
notify() Usually Producer Notify one waiting Thread
notify_all() Usually Producer Notify all waiting Threads

Complete Condition Communication Flow

              Condition
                  │
                  ▼
             Internal Lock
                  │
        ┌─────────┴─────────┐
        │                   │
        ▼                   ▼
     Producer            Consumer
        │                   │
        ▼                   ▼
    acquire()            acquire()
        │                   │
        │                   ▼
        │                wait()
        │                   │
        ▼                   ▼
 Produce Item            Waiting
        │                   ▲
        ▼                   │
 Add to Resource            │
        │                   │
        ▼                   │
    notify() ────────────────┘
        │
        ▼
    release()
                            │
                            ▼
                       Consume Item
                            │
                            ▼
                        release()

Summary

  • A Condition is a more advanced Inter Thread Communication mechanism than Event.
  • A Condition represents a state change such as producing or consuming an item.
  • One or more Threads can wait until another Thread sends a notification.
  • A Condition object is associated with a Lock.
  • The document describes the associated Lock as a Reentrant Lock.
  • acquire() acquires the Condition's internal Lock.
  • release() releases the internal Lock.
  • wait() blocks the Thread until notification or timeout.
  • notify() wakes one waiting Thread.
  • notify_all() wakes all waiting Threads.
  • The Producer generally performs the update.
  • The Producer generally calls notify() or notify_all().
  • The Consumer generally waits for an update.
  • The Consumer generally calls wait().
  • In the second program, the Producer generates a random number and adds it to the items list.
  • The Consumer removes the produced item using items.pop().

Important Notes

  1. Condition is more advanced than Event for Inter Thread Communication.
  2. A Condition object allows one or more Threads to wait for notification.
  3. Condition internally uses a Lock for synchronization.
  4. Use acquire() before performing protected Condition operations.
  5. Use release() after completing the operation.
  6. wait() waits until another Thread sends a notification or the specified waiting time expires.
  7. notify() notifies one waiting Thread.
  8. notify_all() notifies all waiting Threads.
  9. The Consumer generally calls wait() because it is waiting for an update.
  10. The Producer generally calls notify() or notify_all() because it performs the update.
  11. Both Producer and Consumer acquire and release the Condition.
  12. In Demo Program 1, the Consumer waits until the Producer sends a notification.
  13. In Demo Program 2, the Producer generates random items between 1 and 100.
  14. The Producer stores generated items using items.append(item).
  15. The Consumer consumes an item using items.pop().
  16. The Producer and Consumer programs demonstrate synchronization together with communication.

Inter Thread Communication Using Queue

The Queue concept is the most advanced mechanism for Inter Thread Communication and for sharing data between Threads.

In the previous topics, we used:

  • Event
  • Condition

With Condition, the programmer has to manually manage operations such as:

acquire()
wait()
notify()
release()

Queue makes this process easier because synchronization is handled automatically.

Simple Definition:

Queue is an Inter Thread Communication mechanism that allows Threads to safely share data with automatic synchronization.

Why Queue?

A Queue internally contains:

  • Condition
  • Lock

Because of this, whenever we use a Queue, we do not need to worry about synchronization manually.

              Queue
                │
        ┌───────┴───────┐
        │               │
        ▼               ▼
    Condition          Lock
        │               │
        └───────┬───────┘
                ▼
      Automatic Synchronization

The Queue module automatically handles synchronization for us.

Queue in Producer-Consumer Model

Queue is very useful in the Producer–Consumer model.

The Producer creates an item and inserts it into the Queue.

The Consumer removes the item from the Queue and consumes it.

Producer Thread
      │
      ▼
Produce Item
      │
      ▼
q.put(item)
      │
      ▼
   ┌───────┐
   │ Queue │
   └───────┘
      │
      ▼
q.get()
      │
      ▼
Consumer Thread
      │
      ▼
Consume Item

Importing the Queue Module

Before using Queue, we should import the queue module.

import queue

The Queue class is available inside this module.

Creating a Queue Object

We can create a Queue object as follows:

q = queue.Queue()

Here:

  • queue is the module.
  • Queue() creates the Queue object.
  • q is the reference variable.
import queue

q = queue.Queue()

Important Methods of Queue

The document introduces two important Queue methods:

Method Purpose
put() Inserts an item into the Queue
get() Removes and returns an item from the Queue
Producer → put()

Consumer → get()

1. put() Method

The put() method is used to insert an item into the Queue.

Syntax:

q.put(item)

Example:

q.put(10)

This inserts 10 into the Queue.

Before

Queue
┌──────────────┐
│              │
└──────────────┘

q.put(10)

After

Queue
┌──────────────┐
│      10      │
└──────────────┘

The Producer Thread generally uses put().

Working of put()

The Producer Thread uses:

q.put(item)

When put() is executed, Queue automatically performs synchronization.

Conceptually:

Producer Thread
      │
      ▼
q.put(item)
      │
      ▼
Acquire Lock Internally
      │
      ▼
Insert Item
      │
      ▼
Release Lock Automatically
      │
      ▼
Continue

The programmer does not need to manually write:

lock.acquire()

# insert item

lock.release()

Queue handles these operations automatically.

What Happens if the Queue is Full?

The put() method also checks whether the Queue is full.

If the Queue is full:

  • The Producer Thread automatically enters the waiting state.
  • Internally, waiting is handled automatically.
  • The Producer continues only after space becomes available.
Producer
   │
   ▼
q.put(item)
   │
   ▼
Is Queue Full?
   │
 ┌─┴───────┐
 │         │
No        Yes
 │         │
 ▼         ▼
Insert     Wait
Item       │
           ▼
      Space Available
           │
           ▼
       Insert Item

Producer Waiting When Queue is Full

Suppose we have a limited Queue:

q = queue.Queue(2)

Conceptually, it can contain only two items at a time.

Queue Capacity = 2

┌─────────┬─────────┐
│ Item-1  │ Item-2  │
└─────────┴─────────┘

Queue Full

If the Producer tries:

q.put(Item-3)

the Producer waits until the Consumer removes an existing item.

Producer
   │
   ▼
put(Item-3)
   │
   ▼
Queue Full
   │
   ▼
WAIT
   │
   │ Consumer removes Item-1
   ▼
Space Available
   │
   ▼
Insert Item-3

2. get() Method

The get() method removes an item from the Queue and returns that item.

Syntax:

q.get()

Normally we store the returned item:

item = q.get()

Example:

Queue

┌──────────────┐
│      10      │
└──────────────┘

item = q.get()

item = 10

Queue

┌──────────────┐
│              │
└──────────────┘

The Consumer Thread generally uses get().

Working of get()

The Consumer Thread uses:

item = q.get()

When get() executes:

  • The Lock is acquired internally.
  • An item is removed from the Queue.
  • The removed item is returned.
  • The Lock is released automatically.
Consumer Thread
      │
      ▼
q.get()
      │
      ▼
Acquire Lock Internally
      │
      ▼
Remove Item
      │
      ▼
Return Item
      │
      ▼
Release Lock Automatically

Again, no manual synchronization is required.

What Happens if the Queue is Empty?

If the Queue is empty, there is no item available for the Consumer.

In this situation:

  • The Consumer Thread automatically enters the waiting state.
  • Waiting is handled internally.
  • When the Producer inserts a new item, the waiting Consumer is notified automatically.
Consumer
   │
   ▼
q.get()
   │
   ▼
Is Queue Empty?
   │
 ┌─┴───────┐
 │         │
No        Yes
 │         │
 ▼         ▼
Remove     Wait
Item       │
           │
       Producer
       Adds Item
           │
           ▼
        Wake Up
           │
           ▼
      Remove Item

Consumer Waiting Example

Initially:

Queue

┌──────────────┐
│    EMPTY     │
└──────────────┘

The Consumer executes:

item = q.get()

Because the Queue is empty:

Consumer
   │
   ▼
Waiting

Later, the Producer executes:

q.put(49)

Now:

Producer
   │
   ▼
put(49)
   │
   ▼
Queue Gets Item
   │
   ▼
Waiting Consumer
Automatically Continues
   │
   ▼
get()
   │
   ▼
item = 49

Automatic Synchronization

The biggest advantage of Queue is:

The queue module takes care of locking automatically.

With Queue, we do not manually write:

acquire()
release()
wait()
notify()

Queue handles the required locking, waiting, and notification internally.

Without Queue
     │
     ├── acquire()
     ├── wait()
     ├── notify()
     └── release()


With Queue
     │
     ├── put()
     └── get()

Synchronization
Handled Automatically

Complete Producer-Consumer Program Using Queue

🐍Code Cell
1from threading import *
2import time
3import random
4import queue
5 
6def produce(q):
7 while True:
8 item = random.randint(1,100)
9 print("Producer Producing Item:", item)
10 
11 q.put(item)
12 
13 print("Producer giving Notification")
14 time.sleep(5)
15 
16def consume(q):
17 while True:
18 print("Consumer waiting for updation")
19 
20 item = q.get()
21 
22 print("Consumer consumed the item", item)
23 time.sleep(5)
24 
25q = queue.Queue()
26 
27t1 = Thread(target=produce, args=(q,))
28t2 = Thread(target=consume, args=(q,))
29 
30t1.start()
31t2.start()
Output
No output captured.

Sample Output

Important Note About Output

The program contains:

while True:

in both the Producer and Consumer functions.

Therefore, the program continues producing and consuming items repeatedly.

The random values can also change every time because the Producer uses:

random.randint(1,100)

So values such as 49 and 82 are only sample values.

Complete Program Explanation

Step 1: Import threading

from threading import *

This provides the Thread class.


Step 2: Import time

import time

The time module provides sleep().


Step 3: Import random

import random

The Producer uses this module to generate random items.


Step 4: Import queue

import queue

This module provides the Queue class.


Step 5: Define produce()

def produce(q):

The Queue object is passed to the Producer function.


Step 6: Run Producer Continuously

while True:

The Producer continuously creates new items.


Step 7: Generate Random Item

item = random.randint(1,100)

A random integer between 1 and 100 is generated.


Step 8: Display Produced Item

print("Producer Producing Item:", item)

The generated item is displayed.


Step 9: Insert Item into Queue

q.put(item)

The Producer inserts the item into the Queue.

Queue handles the required synchronization automatically.


Step 10: Producer Notification Message

print("Producer giving Notification")

The program displays a message indicating that a new item is available.

The actual Queue coordination is handled internally by put() and get(); the programmer does not explicitly call notify().


Step 11: Producer Sleeps

time.sleep(5)

The Producer waits five seconds before producing the next item.

Consumer Thread - Complete Explanation

Step 1: Define consume()

def consume(q):

The same Queue object is passed to the Consumer.


Step 2: Run Continuously

while True:

The Consumer continuously waits for and consumes items.


Step 3: Display Waiting Message

print("Consumer waiting for updation")

The Consumer displays that it is waiting for an item.


Step 4: Get Item

item = q.get()

This is the most important Consumer operation.

If an item is available:

q.get()
   │
   ▼
Remove Item
   │
   ▼
Return Item

If the Queue is empty:

q.get()
   │
   ▼
Queue Empty
   │
   ▼
Consumer Waits

Step 5: Consume Item

print("Consumer consumed the item", item)

The item removed from the Queue is displayed.


Step 6: Consumer Sleeps

time.sleep(5)

The Consumer waits five seconds before repeating the process.

Creating the Queue

The Queue object is created using:

q = queue.Queue()

The same Queue object is shared by both Threads.

               Queue q
                  ▲
                  │
          ┌───────┴───────┐
          │               │
          │               │
      Producer         Consumer
          │               │
       q.put()          q.get()

Creating Producer and Consumer Threads

The Producer Thread is created using:

t1 = Thread(target=produce, args=(q,))

The Consumer Thread is created using:

t2 = Thread(target=consume, args=(q,))

Both receive the same Queue object:

             q
        ┌────┴────┐
        ▼         ▼
       t1        t2
    Producer   Consumer

Starting the Threads

Both Threads are started using:

t1.start()
t2.start()

After starting, Producer and Consumer execute independently.

The Queue coordinates communication between them.

Producer Execution Flow

Producer Starts
      │
      ▼
Generate Random Item
      │
      ▼
Print Produced Item
      │
      ▼
q.put(item)
      │
      ▼
Queue Stores Item
      │
      ▼
Print Notification
      │
      ▼
Sleep 5 Seconds
      │
      ▼
Repeat

Consumer Execution Flow

Consumer Starts
      │
      ▼
Print Waiting Message
      │
      ▼
q.get()
      │
      ▼
Is Item Available?
      │
   ┌──┴───┐
   │      │
  Yes     No
   │      │
   ▼      ▼
Remove   Wait
Item      │
   │      │
   │   Producer
   │   Inserts Item
   │      │
   └──────┘
      │
      ▼
Print Consumed Item
      │
      ▼
Sleep 5 Seconds
      │
      ▼
Repeat

Complete Producer-Consumer Execution Flow

Program Starts
      │
      ▼
Create Queue
      │
      ▼
Create Producer
and Consumer Threads
      │
      ▼
Start Both Threads
      │
      ▼
Consumer Calls q.get()
      │
      ▼
Queue Empty?
      │
      ▼
Consumer Waits
      │
      ▼
Producer Generates Item
      │
      ▼
Producer Calls q.put(item)
      │
      ▼
Item Added to Queue
      │
      ▼
Waiting Consumer
Can Continue
      │
      ▼
Consumer q.get()
Returns Item
      │
      ▼
Consumer Processes Item
      │
      ▼
Both Sleep
      │
      ▼
Process Repeats

How Queue Automatically Handles Synchronization

Suppose Producer and Consumer try to access the Queue at the same time.

The programmer does not need to write synchronization code manually.

Producer
   │
   ▼
q.put()
   │
   ├───────────────┐
   │               │
   ▼               │
Queue Internal     │
Synchronization    │
   ▲               │
   │               │
   └───────────────┤
                   │
Consumer           │
   │               │
   ▼               │
q.get() ───────────┘

The Queue implementation safely coordinates these operations.

Queue Full vs Queue Empty

Situation Affected Thread Behaviour
Queue has space Producer put() inserts the item
Queue is full Producer put() waits automatically
Queue contains an item Consumer get() removes and returns the item
Queue is empty Consumer get() waits automatically

Advantages of Queue

Using Queue provides the following benefits:

  • Easy Inter Thread Communication.
  • Automatic synchronization.
  • No need to manually use Lock.
  • No need to manually use Condition.
  • Producer automatically waits if the Queue is full.
  • Consumer automatically waits if the Queue is empty.
Queue
 │
 ├── Easy Communication
 │
 ├── Automatic Synchronization
 │
 ├── Automatic Locking
 │
 ├── Automatic Waiting
 │
 └── Automatic Notification

Condition vs Queue

Condition Queue
Programmer manually calls acquire() and release(). Queue automatically handles locking.
Programmer manually uses wait() and notify(). Queue internally handles waiting and notification.
Programmer manages shared data separately. Queue itself provides a structure for sharing data.
Suitable when manual control is required. Suitable for sharing data between Producer and Consumer Threads.

Event vs Condition vs Queue

Feature Event Condition Queue
Communication Level Simple More advanced Most advanced among these mechanisms
Main Concept Signal/Flag Wait and notification Safe data sharing
Manual Lock Management No direct manual Lock required for basic Event use Yes No
Waiting wait() wait() Handled by blocking put()/get()
Notification set() notify() / notify_all() Handled internally
Data Sharing Not its main purpose Shared data managed separately Designed for sharing queued items

Producer and Consumer Method Comparison

Thread Queue Method Purpose
Producer q.put(item) Insert an item
Consumer q.get() Remove and return an item

Easy rule to remember:

Producer → PUT

Consumer → GET

Important Note

The biggest advantage of Queue for Inter Thread Communication is:

The queue module takes care of locking automatically.

Therefore, the programmer does not need to manually manage:

Lock
Condition
acquire()
release()
wait()
notify()

For the normal Producer–Consumer Queue workflow, put() and get() provide the required coordination.

Quick Revision Diagram

                  QUEUE
                    │
          ┌─────────┴─────────┐
          │                   │
          ▼                   ▼
      Producer             Consumer
          │                   │
          ▼                   ▼
    Generate Item         Need Item
          │                   │
          ▼                   │
      q.put(item)             │
          │                   │
          └──────► Queue ◄────┘
                     │
                     ▼
                   q.get()
                     │
                     ▼
                Consume Item


Queue Automatically Handles:

✓ Locking
✓ Waiting
✓ Notification
✓ Synchronization

Summary

  • Queue is the most enhanced mechanism for Inter Thread Communication discussed in the document.
  • Queue is especially useful for sharing data between Threads.
  • Queue internally uses synchronization mechanisms such as Condition and Lock.
  • The Queue module is imported using:
import queue
  • A Queue object is created using:
q = queue.Queue()
  • put() inserts data into the Queue.
  • get() removes and returns data from the Queue.
  • The Producer uses put().
  • The Consumer uses get().
  • put() handles locking internally.
  • get() handles locking internally.
  • If a bounded Queue is full, the Producer waits automatically when performing a blocking put().
  • If the Queue is empty, the Consumer waits automatically when performing a blocking get().
  • When an item becomes available, Queue internally coordinates the waiting Consumer.
  • The programmer does not need to manually use Lock or Condition for this Producer–Consumer communication.
  • Queue makes Inter Thread Communication easier and safer.

Important Notes

  1. Queue is the most advanced Inter Thread Communication mechanism covered in this tutorial.
  2. It is especially useful for sharing data between Producer and Consumer Threads.
  3. The document explains Queue in terms of internally using Condition and Lock.
  4. Because Queue handles synchronization, manual locking is generally not required for Queue operations.
  5. Import Queue support using import queue.
  6. Create a Queue using q = queue.Queue().
  7. The Producer inserts an item using q.put(item).
  8. The Consumer removes and receives an item using q.get().
  9. put() automatically performs the required synchronization.
  10. get() automatically performs the required synchronization.
  11. If a bounded Queue is full, a blocking put() waits until space becomes available.
  12. If a Queue is empty, a blocking get() waits until an item becomes available.
  13. Queue internally handles the required waiting and notification.
  14. The Producer does not need to manually call notify().
  15. The Consumer does not need to manually call wait().
  16. In the document program, the Producer generates values using random.randint(1,100).
  17. The Producer waits five seconds between production cycles.
  18. The Consumer waits five seconds between consumption cycles.
  19. The Producer and Consumer share the same Queue object.
  20. Queue simplifies Producer–Consumer programming compared with manually using Condition.
📝 Key Takeaways
  • Threads communicate to coordinate producer and consumer work
  • Event uses set()/clear() as GREEN and RED signals
  • Condition provides wait() and notify()/notify_all()
  • Queue automatically handles put()/get() synchronization

🧠 Test Your Knowledge

48 Questions
Progress: 0 / 48