Nearby lessons
145 of 159Python - Inter Thread Communication
- 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:
EventConditionQueue- 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:
Eventis provided by the threading module.eventrefers 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
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
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
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
- Inter Thread Communication allows Threads to coordinate with each other.
- Producer and Consumer is the basic example used to understand Thread communication.
- Python supports Event, Condition, Queue, etc. for Inter Thread Communication.
- Event is the simplest mechanism among the mechanisms introduced here.
- An Event object maintains an internal flag.
event.set()changes the flag to True.event.clear()changes the flag to False.event.set()works like a GREEN signal.event.clear()works like a RED signal.event.is_set()checks whether the Event is set.event.wait()blocks the Thread until the Event is set.event.wait(seconds)supports waiting with a timeout.- In the Producer–Consumer example, the Consumer calls
wait(). - The Producer calls
set()after producing the items. - In the Traffic Signal example, Traffic Police calls
set()for GREEN. - Traffic Police calls
clear()for RED. - Drivers use
wait()to wait for GREEN. - 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:
Conditionis the predefined class.conditionis 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:
- Produce an item.
- Acquire the Condition.
- Add the item to the shared resource.
- Notify waiting Consumers.
- Release the Condition.
Producer │ ▼ Generate Item │ ▼ Acquire Condition │ ▼ Add Item │ ▼ Notify Consumer │ ▼ Release Condition
Producer Thread - Pseudo Code
Case Study - Consumer Thread
The Consumer Thread should:
- Acquire the Condition.
- Wait for notification.
- Consume the item.
- Release the Condition.
Consumer │ ▼ Acquire Condition │ ▼ Wait │ ▼ Receive Notification │ ▼ Consume Item │ ▼ Release Condition
Consumer Thread - Pseudo Code
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.
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.
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()ornotify_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
itemslist. - The Consumer removes the produced item using
items.pop().
Important Notes
- Condition is more advanced than Event for Inter Thread Communication.
- A Condition object allows one or more Threads to wait for notification.
- Condition internally uses a Lock for synchronization.
- Use
acquire()before performing protected Condition operations. - Use
release()after completing the operation. wait()waits until another Thread sends a notification or the specified waiting time expires.notify()notifies one waiting Thread.notify_all()notifies all waiting Threads.- The Consumer generally calls
wait()because it is waiting for an update. - The Producer generally calls
notify()ornotify_all()because it performs the update. - Both Producer and Consumer acquire and release the Condition.
- In Demo Program 1, the Consumer waits until the Producer sends a notification.
- In Demo Program 2, the Producer generates random items between 1 and 100.
- The Producer stores generated items using
items.append(item). - The Consumer consumes an item using
items.pop(). - 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:
queueis the module.Queue()creates the Queue object.qis 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
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
- Queue is the most advanced Inter Thread Communication mechanism covered in this tutorial.
- It is especially useful for sharing data between Producer and Consumer Threads.
- The document explains Queue in terms of internally using Condition and Lock.
- Because Queue handles synchronization, manual locking is generally not required for Queue operations.
- Import Queue support using
import queue. - Create a Queue using
q = queue.Queue(). - The Producer inserts an item using
q.put(item). - The Consumer removes and receives an item using
q.get(). put()automatically performs the required synchronization.get()automatically performs the required synchronization.- If a bounded Queue is full, a blocking
put()waits until space becomes available. - If a Queue is empty, a blocking
get()waits until an item becomes available. - Queue internally handles the required waiting and notification.
- The Producer does not need to manually call
notify(). - The Consumer does not need to manually call
wait(). - In the document program, the Producer generates values using
random.randint(1,100). - The Producer waits five seconds between production cycles.
- The Consumer waits five seconds between consumption cycles.
- The Producer and Consumer share the same Queue object.
- Queue simplifies Producer–Consumer programming compared with manually using Condition.
- 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