Python Concurrency: Threading, Asyncio, Multiprocessing Guide
Python Concurrency: Threading, Asyncio, Multiprocessing Guide
Python Concurrency:
Complete Guide
Threading, Asyncio & Multiprocessing
Table of Contents
1. Introduction to Concurrency
2. Threading Module
2.1 Basic Threading
2.2 Synchronization Primitives
2.3 Advanced Threading
3. Asyncio Module
3.1 Basic Asyncio
3.2 Advanced Asyncio
4. Multiprocessing Module
4.1 Basic Multiprocessing
4.2 Synchronization & Communication
4.3 Advanced Multiprocessing
5. Comparison & Best Practices
[Link] 1/36
13/09/2025, 16:07 Python Concurrency Guide
Key Concepts
Note: Python's Global Interpreter Lock (GIL) prevents true parallel execution of
threads for CPU-bound tasks. Use multiprocessing for CPU-intensive operations.
[Link] 2/36
13/09/2025, 16:07 Python Concurrency Guide
2. Threading Module
import threading
import time
# Create threads
thread1 = [Link](target=worker, args=("A", 2))
thread2 = [Link](target=worker, args=("B", 1))
# Start threads
[Link]()
[Link]()
Thread Subclassing
import threading
import time
class WorkerThread([Link]):
def __init__(self, name, delay):
super().__init__()
[Link] = name
[Link] = delay
[Link] = False # Set True for daemon threads
def run(self):
"""Method called when [Link]() is invoked"""
[Link] 3/36
13/09/2025, 16:07 Python Concurrency Guide
print(f"Thread {[Link]} starting")
[Link]([Link])
print(f"Thread {[Link]} finished")
Thread(target,
Create new t = Thread(target=func,
args, kwargs,
thread args=(1,))
daemon)
Start thread
start() [Link]()
execution
Wait for
join(timeout) thread to [Link](5.0)
finish
Check if
is_alive() thread is if thread.is_alive():
running
Get current
current_thread() threading.current_thread()
thread object
[Link] 4/36
13/09/2025, 16:07 Python Concurrency Guide
threads
Get main
main_thread() threading.main_thread()
thread object
[Link] 5/36
13/09/2025, 16:07 Python Concurrency Guide
Lock
import threading
import time
# Shared resource
counter = 0
lock = [Link]()
def increment(n):
global counter
for _ in range(n):
# Method 1: Manual lock management
[Link]()
try:
temp = counter
[Link](0.0001) # Simulate work
counter = temp + 1
finally:
[Link]()
def increment_with_context(n):
global counter
for _ in range(n):
# Method 2: Using context manager (recommended)
with lock:
temp = counter
[Link](0.0001)
counter = temp + 1
# Create threads
threads = []
for i in range(5):
t = [Link](target=increment_with_context, args=(100,))
[Link](t)
[Link]()
for t in threads:
[Link]()
[Link] 6/36
13/09/2025, 16:07 Python Concurrency Guide
import threading
rlock = [Link]()
def recursive_function(n):
with rlock:
print(f"Acquired lock, n={n}")
if n > 0:
recursive_function(n - 1) # Can acquire same lock again
print(f"Releasing lock, n={n}")
Semaphore
import threading
import time
def access_resource(id):
with semaphore:
print(f"Thread {id} accessing resource")
[Link](2)
print(f"Thread {id} releasing resource")
threads = []
for i in range(10):
t = [Link](target=access_resource, args=(i,))
[Link](t)
[Link]()
for t in threads:
[Link]()
Event
import threading
import time
# Create event
event = [Link]()
[Link] 7/36
13/09/2025, 16:07 Python Concurrency Guide
def waiter(name):
print(f"{name} waiting for event")
[Link]() # Block until event is set
print(f"{name} proceeding after event")
def setter():
[Link](3)
print("Setting event")
[Link]()
# Create threads
waiters = [[Link](target=waiter, args=(f"Waiter-{i}",))
for i in range(3)]
setter_thread = [Link](target=setter)
# Event methods
# [Link]() - Set event to true
# [Link]() - Reset event to false
# event.is_set() - Check if event is set
# [Link](timeout) - Wait for event
Condition
import threading
import time
import random
def consumer(name):
with condition:
while len(items) == 0:
print(f"{name} waiting for items")
[Link]() # Release lock and wait
item = [Link](0)
print(f"{name} consumed {item}")
[Link] 8/36
13/09/2025, 16:07 Python Concurrency Guide
def producer():
for i in range(5):
[Link]([Link]())
with condition:
item = f"item-{i}"
[Link](item)
print(f"Produced {item}")
condition.notify_all() # Wake up all waiting threads
# Create threads
consumers = [[Link](target=consumer, args=(f"Consumer-{i}",
for i in range(3)]
producer_thread = [Link](target=producer)
# Start threads
for c in consumers:
[Link]()
producer_thread.start()
Barrier
import threading
import time
import random
def worker(name):
print(f"{name} working...")
[Link]([Link]() * 3)
print(f"{name} waiting at barrier")
try:
index = [Link]() # Wait for all threads
if index == 0:
print("All threads reached barrier!")
except [Link]:
print(f"{name}: Barrier is broken")
[Link] 9/36
13/09/2025, 16:07 Python Concurrency Guide
for i in range(3):
t = [Link](target=worker, args=(f"Worker-{i}",))
[Link](t)
[Link]()
for t in threads:
[Link]()
[Link] 10/36
13/09/2025, 16:07 Python Concurrency Guide
def process_item(item):
"""Simulate processing"""
[Link](1)
return item * 2
[Link] 11/36
13/09/2025, 16:07 Python Concurrency Guide
print(f"Item {i} raised: {e}")
import threading
import time
def process_with_context(name):
# Set thread-local data
local_data.name = name
local_data.counter = 0
for i in range(3):
local_data.counter += 1
print(f"Thread {local_data.name}: {local_data.counter}")
[Link](0.5)
threads = []
for i in range(3):
t = [Link](target=process_with_context, args=(f"Worker-
[Link](t)
[Link]()
for t in threads:
[Link]()
Timer Threads
import threading
def delayed_action():
print("Timer expired! Action executed.")
[Link] 12/36
13/09/2025, 16:07 Python Concurrency Guide
[Link]()
[Link] 13/36
13/09/2025, 16:07 Python Concurrency Guide
3. Asyncio Module
import asyncio
import time
# Basic coroutine
async def hello_world():
print("Hello")
await [Link](1)
print("World")
return "Done"
# Running a coroutine
# Method 1: [Link]() (Python 3.7+)
result = [Link](hello_world())
print(f"Result: {result}")
# Multiple coroutines
async def task(name, delay):
print(f"Task {name} starting")
await [Link](delay)
print(f"Task {name} completed")
return f"Result-{name}"
[Link](main())
[Link] 14/36
13/09/2025, 16:07 Python Concurrency Guide
Run async
[Link]() [Link](main())
function
Schedule task =
asyncio.create_task()
coroutine asyncio.create_task(coro())
Wait for
done, pending = await
[Link]() multiple
[Link](tasks)
tasks
await
Wait with
asyncio.wait_for() asyncio.wait_for(coro(),
timeout
timeout=5)
Protect from
[Link]() await [Link](task)
cancellation
Iterate as
for coro in
asyncio.as_completed() tasks
asyncio.as_completed(tasks):
complete
import asyncio
# Method 4: As completed
print("\nAs completed:")
tasks = [asyncio.create_task(fetch_data(url)) for url in urls]
for coro in asyncio.as_completed(tasks):
result = await coro
print(result)
[Link](main())
import asyncio
[Link] 16/36
13/09/2025, 16:07 Python Concurrency Guide
[Link](main())
[Link] 17/36
13/09/2025, 16:07 Python Concurrency Guide
Asyncio Queue
import asyncio
import random
consumers = [
asyncio.create_task(consumer(queue, f"C{i}"))
for i in range(3)
]
# Stop consumers
for _ in consumers:
await [Link](None)
await [Link](*consumers)
[Link] 18/36
13/09/2025, 16:07 Python Concurrency Guide
[Link](main())
import asyncio
# Shared resource
counter = 0
[Link](main())
[Link] 19/36
13/09/2025, 16:07 Python Concurrency Guide
import asyncio
# Using Condition
condition = [Link]()
shared_data = []
await [Link](
consumer_with_condition(condition, shared_data, "C1"),
consumer_with_condition(condition, shared_data, "C2"),
producer_with_condition(condition, shared_data)
)
[Link](main())
[Link] 20/36
13/09/2025, 16:07 Python Concurrency Guide
import asyncio
try:
await task
except [Link]:
print("Task was cancelled")
try:
await asyncio.wait_for(shielded, timeout=1.0)
except [Link]:
print("Timed out, but task continues")
# Task continues running despite timeout
[Link](main())
[Link] 21/36
13/09/2025, 16:07 Python Concurrency Guide
4. Multiprocessing Module
Creating Processes
import multiprocessing
import os
import time
# Start processes
[Link]()
[Link]()
Process Subclassing
import multiprocessing
import os
import time
class WorkerProcess([Link]):
def __init__(self, name, delay):
super().__init__()
[Link] = name
[Link] = delay
[Link] 22/36
13/09/2025, 16:07 Python Concurrency Guide
def run(self):
print(f"Process {[Link]} started, PID: {[Link]()}")
[Link]([Link])
print(f"Process {[Link]} finished")
if __name__ == "__main__":
processes = []
for i in range(3):
p = WorkerProcess(f"Worker-{i}", i + 1)
[Link]()
[Link](p)
for p in processes:
[Link]()
print(f"Process {[Link]} exited with code {[Link]}")
Process Pool
import multiprocessing
import time
def cpu_bound_task(n):
"""CPU-intensive task"""
total = 0
for i in range(n * 1000000):
total += i
return total
if __name__ == "__main__":
# Create pool with 4 worker processes
with [Link](processes=4) as pool:
# Method 1: map()
inputs = [10, 20, 30, 40, 50]
results = [Link](cpu_bound_task, inputs)
print(f"Map results: {results}")
# Method 2: map_async()
async_result = pool.map_async(cpu_bound_task, inputs)
results = async_result.get()
print(f"Map async results: {results}")
# Method 3: apply()
result = [Link](cpu_bound_task, args=(25,))
print(f"Apply result: {result}")
# Method 4: apply_async()
async_results = []
for i in inputs:
[Link] 23/36
13/09/2025, 16:07 Python Concurrency Guide
async_result = pool.apply_async(cpu_bound_task, args=(i,)
async_results.append(async_result)
Start
start() [Link]()
process
Wait for
join(timeout) [Link](5)
process
Terminate
terminate() [Link]()
process
Check if
is_alive() if process.is_alive():
running
Get current
current_process() multiprocessing.current_process()
process
Number of
cpu_count() multiprocessing.cpu_count()
CPUs
[Link] 24/36
13/09/2025, 16:07 Python Concurrency Guide
Create
Pool(processes) Pool(processes=4)
process pool
[Link] 25/36
13/09/2025, 16:07 Python Concurrency Guide
Queue
import multiprocessing
import time
if __name__ == "__main__":
# Create queue
queue = [Link]()
# Create processes
producers = [
[Link](target=producer, args=(queue, f"P{i}"
for i in range(2)
]
consumers = [
[Link](target=consumer, args=(queue, f"C{i}"
for i in range(3)
]
[Link] 26/36
13/09/2025, 16:07 Python Concurrency Guide
# Terminate consumers
for c in consumers:
[Link]()
[Link]()
Pipe
import multiprocessing
def sender(conn):
messages = ["Hello", "World", {"key": "value"}, [1, 2, 3]]
for msg in messages:
[Link](msg)
print(f"Sent: {msg}")
[Link]()
def receiver(conn):
while True:
try:
msg = [Link]()
print(f"Received: {msg}")
except EOFError:
break
[Link]()
if __name__ == "__main__":
# Create pipe (returns two connection objects)
parent_conn, child_conn = [Link]()
# Create processes
p1 = [Link](target=sender, args=(child_conn,))
p2 = [Link](target=receiver, args=(parent_conn,)
# Start processes
[Link]()
[Link]()
Shared Memory
import multiprocessing
import ctypes
[Link] 27/36
13/09/2025, 16:07 Python Concurrency Guide
def worker_with_value(shared_value, lock, name):
for _ in range(100):
with lock:
temp = shared_value.value
shared_value.value = temp + 1
print(f"{name} finished")
if __name__ == "__main__":
# Shared Value
shared_value = [Link]('i', 0) # 'i' for integer
lock = [Link]()
processes = []
for i in range(4):
p = [Link](
target=worker_with_value,
args=(shared_value, lock, f"Worker-{i}")
)
[Link]()
[Link](p)
for p in processes:
[Link]()
# Shared Array
shared_array = [Link]('d', 10) # 'd' for double
processes = []
for i in range(10):
p = [Link](
target=worker_with_array,
args=(shared_array, i)
)
[Link]()
[Link](p)
for p in processes:
[Link]()
Manager
[Link] 28/36
13/09/2025, 16:07 Python Concurrency Guide
import multiprocessing
if __name__ == "__main__":
# Create manager
manager = [Link]()
[Link] 29/36
13/09/2025, 16:07 Python Concurrency Guide
def cpu_intensive_task(n):
"""Simulate CPU-intensive work"""
total = sum(i * i for i in range(n * 1000000))
return f"Task {n}: {total}"
if __name__ == "__main__":
# Using ProcessPoolExecutor
with ProcessPoolExecutor(max_workers=4) as executor:
# Submit tasks
futures = []
for i in range(10):
future = [Link](cpu_intensive_task, i)
[Link](future)
# Using map
with ProcessPoolExecutor(max_workers=4) as executor:
inputs = range(10)
results = [Link](cpu_intensive_task, inputs)
for result in results:
print(result)
import multiprocessing
import logging
import sys
def configure_logging():
"""Configure logging for multiprocessing"""
logger = multiprocessing.get_logger()
[Link]([Link])
handler = [Link]([Link])
formatter = [Link](
[Link] 30/36
13/09/2025, 16:07 Python Concurrency Guide
'[%(levelname)s/%(processName)s] %(message)s'
)
[Link](formatter)
[Link](handler)
return logger
def worker_with_logging(name):
logger = multiprocessing.get_logger()
[Link](f"Worker {name} starting")
# Do work
[Link](f"Worker {name} finished")
if __name__ == "__main__":
configure_logging()
processes = []
for i in range(3):
p = [Link](
target=worker_with_logging,
args=(f"Worker-{i}",)
)
[Link]()
[Link](p)
for p in processes:
[Link]()
import multiprocessing
import traceback
if __name__ == "__main__":
result_queue = [Link]()
[Link] 31/36
13/09/2025, 16:07 Python Concurrency Guide
processes = []
for i in range(5):
p = [Link](
target=risky_worker,
args=(i, result_queue)
)
[Link]()
[Link](p)
for p in processes:
[Link]()
# Collect results
while not result_queue.empty():
status, n, data = result_queue.get()
if status == "success":
print(f"Success for {n}: {data}")
else:
print(f"Error for {n}: {data['error']}")
print(f"Traceback: {data['traceback']}")
[Link] 32/36
13/09/2025, 16:07 Python Concurrency Guide
import time
import threading
import asyncio
import multiprocessing
from [Link] import ThreadPoolExecutor, ProcessPoolExecuto
# CPU-bound task
def cpu_bound(n):
return sum(i * i for i in range(n))
# I/O-bound task
def io_bound(n):
[Link] 33/36
13/09/2025, 16:07 Python Concurrency Guide
[Link](0.1)
return n
if __name__ == "__main__":
# Test data
cpu_inputs = [1000000] * 10
io_inputs = list(range(20))
Best Practices
[Link] 34/36
13/09/2025, 16:07 Python Concurrency Guide
Common Mistakes:
Conclusion
Choose the right tool based on your specific use case, and always consider the trade-
offs between complexity and performance gains.
[Link] 35/36
13/09/2025, 16:07 Python Concurrency Guide
Final Tips:
• Profile your code to identify bottlenecks before optimizing
• Start simple and add concurrency only when needed
• Test thoroughly, concurrent code can have subtle bugs
• Consider using higher-level libraries built on these primitives
import asyncio
print(f"Command: {cmd}")
print(f"Return code: {[Link]}")
print(f"Output: {[Link]()}")
if stderr:
print(f"Error: {[Link]()}")
[Link] 36/36