Nearby lessons
142 of 159Python - Synchronization Using Lock
- Create a Lock object with threading.Lock()
- Use acquire() and release() correctly
- Understand that only one thread can hold a lock at a time
- Explain the release() RuntimeError on an unlocked lock
- Analyze the synchronized wish() example
Synchronization Using Lock
Python provides different synchronization mechanisms.
One of the most fundamental synchronization mechanisms provided by the threading module is:
Lock
A Lock is used to make sure that only one Thread can access a particular critical section at a time.
Simple Definition:
A Lock allows only one Thread at a time to execute the protected code.
Multiple Threads
│
▼
Lock
│
▼
Only One Thread
Gets Permission
│
▼
Critical Section
Why Do We Need Lock?
Without synchronization, multiple Threads may execute the same shared code simultaneously.
This can result in:
- Irregular output
- Data inconsistency
A Lock controls access to the shared resource.
Without Lock Thread-1 ───┐ Thread-2 ───┼──► Shared Resource Thread-3 ───┘ Possible overlapping execution With Lock Thread-1 ───► Lock ───► Shared Resource Thread-2 ───► Wait Thread-3 ───► Wait
Creating a Lock Object
We can create a Lock object as follows:
l = Lock()
For this, Lock is imported from the threading module.
from threading import * l = Lock()
Here:
Lock()creates a Lock object.lis the reference variable pointing to the Lock object.- The same Lock can be shared by multiple Threads.
How Does a Lock Work?
The Lock object can be held by only one Thread at a time.
If another Thread requires the same Lock, it must wait until the current Thread releases the Lock.
Thread-1 │ ▼ Acquire Lock │ ▼ Lock Occupied │ ├──────────── Thread-2 → Wait │ └──────────── Thread-3 → Wait │ ▼ Thread-1 Executes │ ▼ Release Lock │ ▼ One Waiting Thread Gets the Lock
Real-Life Comparison
The document compares a Lock with examples such as:
- Common wash rooms
- Public telephone booths
Consider a public telephone booth.
Person-1 │ ▼ Using Booth │ ├──── Person-2 → Wait ├──── Person-3 → Wait │ ▼ Person-1 Leaves │ ▼ Next Person Enters
Only one person can use the resource at a time.
A Thread Lock works using the same basic idea.
acquire() Method
A Thread can acquire the Lock by using the acquire() method.
Syntax:
l.acquire()
When a Thread successfully executes:
l.acquire()
that Thread gets the Lock.
Thread │ ▼ l.acquire() │ ▼ Is Lock Available? │ ├──── Yes ───► Acquire Lock │ └──── No ─────► Wait
What Happens if the Lock is Already Acquired?
Suppose Thread-1 already holds the Lock.
Thread-1 │ ▼ l.acquire() │ ▼ Lock Acquired
Now Thread-2 executes:
l.acquire()
But the Lock is not available.
Therefore:
Thread-2 │ ▼ l.acquire() │ ▼ Lock Already Held by Thread-1 │ ▼ Thread-2 Waits
Thread-2 can continue only after the Lock becomes available.
release() Method
A Thread can release the Lock by using the release() method.
Syntax:
l.release()
Once the Lock is released, another waiting Thread can acquire it.
Thread-1 │ ▼ Critical Section │ ▼ l.release() │ ▼ Lock Available │ ▼ Another Waiting Thread Can Acquire Lock
acquire() and release()
The normal Lock execution pattern is:
l.acquire() # Critical Section # Shared Resource Operations l.release()
The sequence is:
Acquire │ ▼ Execute Protected Code │ ▼ Release
| Method | Purpose |
|---|---|
acquire() |
Acquire the Lock |
release() |
Release the Lock |
Important Rule of release()
The document gives an important rule:
To call
release(), the Lock must already be in the acquired state.
If we try to release an unlocked Lock, Python raises:
RuntimeError: release unlocked lock
Therefore, the normal sequence should be:
l.acquire()
│
▼
Lock Acquired
│
▼
l.release()
│
▼
Lock Released
Not:
Lock Not Acquired
│
▼
l.release()
│
▼
RuntimeError
Example - release() Without acquire()
Output When Line-1 is Commented
Program Explanation - release() Error
Step 1: Import threading
from threading import *
The required Threading classes and functions are imported.
Step 2: Create the Lock
l = Lock()
A new Lock object is created.
Initially, the Lock is not acquired.
Step 3: acquire() is Commented
# l.acquire()
Because this statement is commented, the Lock remains unlocked.
Step 4: Call release()
l.release()
Python is asked to release a Lock that is currently unlocked.
Therefore, Python raises:
RuntimeError: release unlocked lock
Execution Flow - release unlocked lock
Program Starts
│
▼
Create Lock
│
▼
Lock is Unlocked
│
▼
l.acquire()
Commented
│
▼
Execute
l.release()
│
▼
Trying to Release
Unlocked Lock
│
▼
RuntimeError:
release unlocked lock
What Happens if Line-1 is Not Commented?
If we execute:
l.acquire()
before:
l.release()
the sequence becomes valid.
from threading import * l = Lock() l.acquire() l.release()
Execution:
Create Lock
│
▼
Acquire Lock
│
▼
Lock Becomes Locked
│
▼
Release Lock
│
▼
Lock Becomes Unlocked
│
▼
Program Ends Normally
Synchronization Example Using Lock
Now the document applies Lock to the earlier wish() synchronization problem.
Three Threads are created:
- Dhoni
- Yuvraj
- Kohli
All three Threads execute the same wish() function.
But before entering the protected code, each Thread must acquire the same Lock.
Complete Program - Synchronization Using Lock
Output
The document does not print the complete output of this program.
It states:
At a time only one Thread is allowed to execute the
wish()method and hence we will get regular output.
Conceptually, one possible output order is:
Good Evening:Dhoni Good Evening:Dhoni Good Evening:Dhoni ... 10 times Good Evening:Yuvraj Good Evening:Yuvraj Good Evening:Yuvraj ... 10 times Good Evening:Kohli Good Evening:Kohli Good Evening:Kohli ... 10 times
The exact order in which Dhoni, Yuvraj, and Kohli acquire the Lock is not guaranteed.
For example, Yuvraj may acquire the Lock first. The important point is that the outputs of the protected wish() executions do not mix with each other.
Complete Program Explanation
Step 1: Import Required Modules
from threading import * import time
The threading module provides Thread and Lock functionality.
The time module provides sleep().
Step 2: Create the Lock Object
l = Lock()
A single Lock object is created.
This Lock is shared by all three Threads.
Lock l
▲
┌────┼────┐
│ │ │
│ │ │
t1 t2 t3
Step 3: Create the Shared Function
def wish(name):
All three Threads execute the same wish() function.
Step 4: Acquire the Lock
l.acquire()
Before entering the critical section, the Thread must acquire the Lock.
Only one Thread can successfully acquire the Lock at a time.
The remaining Threads wait.
Step 5: Execute the Loop
for i in range(10):
The Thread that owns the Lock executes the loop ten times.
Step 6: Print Good Evening
print("Good Evening:", end='')
The greeting is printed without moving to the next line.
Step 7: Sleep for Two Seconds
time.sleep(2)
The currently executing Thread waits for two seconds.
Even while it is sleeping, it still holds the Lock because release() has not yet been called.
Therefore, the other Threads cannot enter the protected section using this Lock.
Step 8: Print the Name
print(name)
The Thread prints its corresponding name.
Step 9: Complete All Ten Iterations
The Thread continues until all ten iterations are completed.
Step 10: Release the Lock
l.release()
After completing the entire loop, the Thread releases the Lock.
Now one of the waiting Threads can acquire it.
Step 11: Create Three Threads
t1 = Thread(target=wish, args=("Dhoni",))
t2 = Thread(target=wish, args=("Yuvraj",))
t3 = Thread(target=wish, args=("Kohli",))
| Thread | Target | Argument |
|---|---|---|
t1 |
wish() |
Dhoni |
t2 |
wish() |
Yuvraj |
t3 |
wish() |
Kohli |
Step 12: Start All Threads
t1.start() t2.start() t3.start()
All three Threads start and request the same Lock.
Only one gets the Lock.
The remaining two wait until the Lock becomes available.
How Three Threads Use One Lock
Lock l
│
┌────────┼────────┐
│ │ │
▼ ▼ ▼
t1 t2 t3
│ │ │
└────────┼────────┘
│
▼
Who Acquires
Lock First?
│
▼
One Thread
│
▼
wish(name)
│
▼
10 Iterations
│
▼
release()
│
▼
Next Waiting
Thread
Why Is the Output Regular?
The output becomes regular because:
- Only one Thread can hold the Lock at a time.
- The remaining Threads wait until the Lock is released.
- Only the Lock-owning Thread executes the protected
wish()work. - The Lock is released only after all ten iterations are completed.
Therefore, we do not get the earlier mixed output such as:
Good Evening:Good Evening:Yuvraj Dhoni
Instead, each Thread completes its protected execution before another waiting Thread gets the Lock.
Lock is Held During sleep()
An important point in this program is:
l.acquire()
for i in range(10):
print("Good Evening:", end='')
time.sleep(2)
print(name)
l.release()
The Lock is acquired before the loop and released after the loop.
Therefore, even when the current Thread executes:
time.sleep(2)
it does not release the Lock.
Thread-1 │ ▼ Acquire Lock │ ▼ Print │ ▼ sleep(2) │ │ Lock Still Held │ ├──── Thread-2 → Waiting └──── Thread-3 → Waiting │ ▼ Continue Loop
Execution Flow
Program Starts
│
▼
Create Lock
│
▼
Create t1, t2, t3
│
▼
Start All Threads
│
▼
All Request Same Lock
│
▼
One Thread Acquires Lock
│
├──────────────► Other Thread Waits
│
└──────────────► Other Thread Waits
│
▼
Execute wish()
10 Times
│
▼
Release Lock
│
▼
One Waiting Thread
Acquires Lock
│
▼
Execute wish()
10 Times
│
▼
Release Lock
│
▼
Last Waiting Thread
Acquires Lock
│
▼
Execute wish()
10 Times
│
▼
Release Lock
│
▼
All Threads Complete
│
▼
Program Ends
Without Lock vs With Lock
| Without Lock | With Lock |
|---|---|
| Multiple Threads may enter shared code simultaneously. | Only one Thread can hold the Lock at a time. |
| Execution overlaps. | Protected execution is controlled. |
| Output can become mixed. | Output becomes regular. |
| Data inconsistency may occur. | Data inconsistency can be avoided. |
| No Thread waits for a Lock. | Other Threads wait while the Lock is held. |
Lock State
A simple Lock can be understood using two states:
| State | Meaning |
|---|---|
| Unlocked | The Lock is available to be acquired. |
| Locked | The Lock has already been acquired. |
Unlocked │ │ acquire() ▼ Locked │ │ release() ▼ Unlocked
Lock Method Comparison
| Method | Purpose | Result |
|---|---|---|
l.acquire() |
Request the Lock | Thread acquires it if available; otherwise it waits |
l.release() |
Release the Lock | Lock becomes available |
Important Error
Remember this error:
RuntimeError: release unlocked lock
It occurs when:
l = Lock() l.release()
because the Lock is already unlocked.
The valid order is:
l = Lock() l.acquire() l.release()
Summary
Lockis a fundamental synchronization mechanism provided by thethreadingmodule.- A Lock object is created using:
l = Lock()
- A Lock can be held by only one Thread at a time.
- If another Thread requires the same Lock, it waits until the current Thread releases it.
- A Thread acquires the Lock using:
l.acquire()
- A Thread releases the Lock using:
l.release()
- Calling
release()when the Lock is not acquired raises:
RuntimeError: release unlocked lock
- Using a Lock ensures that only one Thread executes the protected critical section at a time.
- This produces regular output in the
wish()example.
Important Notes
- A Lock object allows only one Thread to hold the Lock at a time.
- Other Threads wait until the Lock becomes available.
- Use
acquire()before entering the critical section. - Use
release()after completing the critical section. - The Lock must be in the acquired state before
release()is called. - Calling
release()on an unlocked Lock results inRuntimeError: release unlocked lock. - In the synchronization example, all three Threads share the same Lock.
- The Threads execute the same
wish()function. - Thread
t1receives"Dhoni". - Thread
t2receives"Yuvraj". - Thread
t3receives"Kohli". - Only one Thread can execute the Lock-protected
wish()work at a time. - The other Threads wait until the Lock is released.
- The Lock is acquired before the loop and released after all ten iterations.
- Therefore, the Thread continues holding the Lock even during
time.sleep(2). - The synchronized program produces regular output instead of the mixed output seen without synchronization.
- A Lock allows only one thread in the critical section at a time
- Other threads block while the lock is held
- Calling release() on an unlocked lock raises RuntimeError
- The synchronized wish() program produces regular output