- What is thread creation — spawning new threads
- The threading module — Python's thread library
- Creating threads with functions — target-based approach
- Creating threads with classes — subclassing Thread
- Thread pools — managing multiple threads
- Daemon threads — background vs foreground
- Thread lifecycle — from creation to termination
What is Thread Creation?
Thread creation is the process of spawning new threads in your Python program. Each thread runs independently and can execute tasks concurrently, making your program more efficient and responsive.
👨💼 Think of it like hiring employees.
You (the main thread) can hire employees (create threads) to handle different tasks. Each employee works independently, and you can manage them by assigning tasks, monitoring progress, and waiting for them to finish.
📝 Function-based
Create threads using target functions
📦 Class-based
Subclass Thread for custom behavior
🏊 Thread Pools
Manage multiple threads efficiently
⚙️ Daemon Threads
Background threads that auto-terminate
💡 Key concept: The threading module provides all the tools you need to create and manage threads in Python.
The Threading Module
Python's Threading Module
The threading module is Python's built-in library for thread creation and management. It provides high-level thread APIs and is the recommended way to work with threads.
# ============================================================
# INTRODUCTION TO THREADING MODULE
# ============================================================
import threading
import time
print("=" * 40)
print("THREADING MODULE BASICS")
print("=" * 40)
# ============================================================
# CHECKING THREAD INFORMATION
# ============================================================
print(f"Main thread name: {threading.main_thread().name}")
print(f"Active threads count: {threading.active_count()}")
print(f"Current thread name: {threading.current_thread().name}")
print(f"Current thread ident: {threading.get_ident()}")
print("\nActive threads:")
for t in threading.enumerate():
print(f" - {t.name} (alive: {t.is_alive()})")
# ============================================================
# SIMPLE THREAD CREATION
# ============================================================
def worker():
"""Simple worker function"""
thread_name = threading.current_thread().name
print(f" {thread_name}: Worker started")
time.sleep(1)
print(f" {thread_name}: Worker finished")
print("\n" + "=" * 40)
print("CREATING A THREAD")
print("=" * 40)
# Create a thread
t = threading.Thread(target=worker, name="WorkerThread")
print(f"Created thread: {t.name}")
# Start the thread
t.start()
print(f"Thread {t.name} started")
# Wait for thread to finish
t.join()
print("Thread completed!")
print("""
┌─────────────────────────────────────────────────────────────────┐
│ THREADING MODULE KEY FEATURES: │
├─────────────────────────────────────────────────────────────────┤
│ • Thread class for creating threads │
│ • active_count() - number of active threads │
│ • current_thread() - get current thread │
│ • enumerate() - list all active threads │
│ • main_thread() - get main thread │
└─────────────────────────────────────────────────────────────────┘
""")
Threading module key points:
- Thread class — main API for thread creation
- Active threads — track running threads
- Thread identification — name and ident
- Built-in — no external dependencies
Quick Check: What module provides thread support in Python? (Answer: the threading module)
Creating Threads with Functions
Threads with Target Functions
The most common way to create a thread is by passing a function to the Thread constructor. The function will run in the new thread when start() is called.
# ============================================================
# CREATING THREADS WITH FUNCTIONS
# ============================================================
import threading
import time
import random
print("=" * 40)
print("FUNCTION-BASED THREADS")
print("=" * 40)
# ============================================================
# BASIC FUNCTION THREAD
# ============================================================
def greet(name, delay=1):
"""Simple function to run in a thread"""
print(f"Hello, {name}! Thread starting...")
time.sleep(delay)
print(f"Goodbye, {name}! Thread ending.")
print("\n1. Basic thread with function:")
t = threading.Thread(target=greet, args=("Alice", 0.5))
t.start()
t.join()
# ============================================================
# THREAD WITH MULTIPLE ARGUMENTS
# ============================================================
def process_data(data_id, count, delay=0.3):
"""Process data with multiple arguments"""
thread_name = threading.current_thread().name
print(f"[{thread_name}] Processing data {data_id} ({count} items)")
for i in range(count):
time.sleep(delay)
print(f"[{thread_name}] Item {i+1}/{count} processed")
print(f"[{thread_name}] Data {data_id} complete!")
print("\n2. Thread with multiple arguments:")
t = threading.Thread(
target=process_data,
args=(101, 3),
kwargs={"delay": 0.2},
name="DataProcessor"
)
t.start()
t.join()
# ============================================================
# MULTIPLE THREADS WITH SAME FUNCTION
# ============================================================
def task(task_id):
"""Worker task with random sleep"""
duration = random.uniform(0.3, 0.8)
print(f" Task {task_id}: started (duration: {duration:.2f}s)")
time.sleep(duration)
print(f" Task {task_id}: completed!")
print("\n3. Multiple threads with same function:")
threads = []
for i in range(5):
t = threading.Thread(target=task, args=(i,), name=f"Worker-{i}")
threads.append(t)
t.start()
print("All threads started, waiting for completion...")
for t in threads:
t.join()
print("All tasks completed!")
# ============================================================
# LAMBDA FUNCTIONS IN THREADS
# ============================================================
print("\n4. Lambda function in thread:")
t = threading.Thread(
target=lambda: print(f" Lambda thread {threading.current_thread().name}: Hello!"),
name="LambdaThread"
)
t.start()
t.join()
print("""
┌─────────────────────────────────────────────────────────────────┐
│ FUNCTION-BASED THREAD ADVANTAGES: │
├─────────────────────────────────────────────────────────────────┤
│ • Simple and straightforward │
│ • Reusable functions │
│ • Easy to pass arguments │
│ • Works with lambdas │
├─────────────────────────────────────────────────────────────────┤
│ FUNCTION-BASED THREAD DISADVANTAGES: │
├─────────────────────────────────────────────────────────────────┤
│ • Limited thread-specific customization │
│ • Cannot override Thread methods │
│ • Less control over thread lifecycle │
└─────────────────────────────────────────────────────────────────┘
""")
Function-based creation key points:
- Target function — the function to run
- Arguments — pass via args and kwargs
- Thread name — optional naming
- Simple — quick and easy to implement
Quick Check: How do you pass arguments to a thread's target function? (Answer: Using the args and kwargs parameters)
Creating Threads with Classes
Threads by Subclassing Thread
You can create threads by subclassing the Thread class and overriding the run() method. This gives you more control and allows for custom thread-specific behavior.
# ============================================================
# CREATING THREADS WITH CLASSES
# ============================================================
import threading
import time
import random
print("=" * 40)
print("CLASS-BASED THREADS")
print("=" * 40)
# ============================================================
# BASIC THREAD SUBCLASS
# ============================================================
class MyThread(threading.Thread):
"""Custom thread class"""
def __init__(self, name, delay=1):
super().__init__(name=name)
self.delay = delay
def run(self):
"""Override the run method"""
print(f" {self.name}: Starting (delay: {self.delay}s)")
time.sleep(self.delay)
print(f" {self.name}: Finishing")
print("\n1. Basic thread subclass:")
t = MyThread("CustomThread", delay=0.5)
t.start()
t.join()
# ============================================================
# ADVANCED THREAD SUBCLASS WITH DATA
# ============================================================
class DataProcessor(threading.Thread):
"""Thread that processes data"""
def __init__(self, data_id, items, name=None):
super().__init__(name=name)
self.data_id = data_id
self.items = items
self.result = None
def run(self):
"""Process data and store result"""
print(f"[{self.name}] Processing data {self.data_id}")
total = 0
for i, item in enumerate(self.items):
time.sleep(0.1)
total += item
print(f"[{self.name}] Item {i+1}/{len(self.items)}: {item}")
self.result = total
print(f"[{self.name}] Data {self.data_id} complete! Total: {total}")
print("\n2. Advanced thread subclass with data:")
processor = DataProcessor(
data_id=123,
items=[10, 20, 30, 40, 50],
name="DataProcessor"
)
processor.start()
processor.join()
print(f"Result: {processor.result}")
# ============================================================
# THREAD WITH ERROR HANDLING
# ============================================================
class SafeThread(threading.Thread):
"""Thread with built-in error handling"""
def __init__(self, target, args=(), kwargs=None, name=None):
super().__init__(name=name)
self.target = target
self.args = args
self.kwargs = kwargs or {}
self.exception = None
def run(self):
"""Run with error handling"""
try:
self.target(*self.args, **self.kwargs)
except Exception as e:
self.exception = e
print(f"[{self.name}] Error: {e}")
def faulty_function():
"""Function that raises an error"""
time.sleep(0.2)
raise ValueError("Something went wrong!")
print("\n3. Thread with error handling:")
safe_thread = SafeThread(
target=faulty_function,
name="SafeThread"
)
safe_thread.start()
safe_thread.join()
if safe_thread.exception:
print(f"Caught exception: {safe_thread.exception}")
else:
print("Thread completed successfully!")
print("""
┌─────────────────────────────────────────────────────────────────┐
│ CLASS-BASED THREAD ADVANTAGES: │
├─────────────────────────────────────────────────────────────────┤
│ • More control and customization │
│ • Can store thread-specific data │
│ • Override methods (run, __init__, etc.) │
│ • Better encapsulation │
├─────────────────────────────────────────────────────────────────┤
│ CLASS-BASED THREAD DISADVANTAGES: │
├─────────────────────────────────────────────────────────────────┤
│ • More verbose than function-based │
│ • Slightly more complex │
│ • Overkill for simple tasks │
└─────────────────────────────────────────────────────────────────┘
""")
Class-based creation key points:
- Subclass Thread — inherit from threading.Thread
- Override run() — define thread behavior
- Custom attributes — store thread-specific data
- Encapsulation — better organization
Quick Check: What method must you override when subclassing Thread? (Answer: the run() method)
Thread Pools
Managing Threads with Pools
A thread pool manages a collection of worker threads, making it easy to execute many tasks without creating a new thread for each one. This is more efficient and scalable.
# ============================================================
# THREAD POOLS WITH ThreadPoolExecutor
# ============================================================
from concurrent.futures import ThreadPoolExecutor
import threading
import time
import random
print("=" * 40)
print("THREAD POOLS")
print("=" * 40)
# ============================================================
# BASIC THREAD POOL
# ============================================================
def worker(task_id):
"""Worker function for thread pool"""
duration = random.uniform(0.2, 0.6)
thread_name = threading.current_thread().name
print(f" [{thread_name}] Task {task_id}: started ({duration:.2f}s)")
time.sleep(duration)
result = f"Task {task_id} result"
print(f" [{thread_name}] Task {task_id}: completed")
return result
print("\n1. Thread pool with 3 workers:")
with ThreadPoolExecutor(max_workers=3) as executor:
# Submit tasks and get futures
futures = [executor.submit(worker, i) for i in range(6)]
# Get results as they complete
for future in futures:
result = future.result()
print(f" Got result: {result}")
# ============================================================
# MAP WITH THREAD POOL
# ============================================================
def square(n):
"""Compute square"""
time.sleep(0.1)
return n * n
print("\n2. Using map() with thread pool:")
with ThreadPoolExecutor(max_workers=4) as executor:
results = executor.map(square, range(10))
print(f" Squares: {list(results)}")
# ============================================================
# ASYNCHRONOUS CALLBACKS
# ============================================================
def callback(future):
"""Callback function"""
print(f" Callback received: {future.result()}")
print("\n3. Asynchronous callbacks:")
with ThreadPoolExecutor(max_workers=2) as executor:
future = executor.submit(worker, 999)
future.add_done_callback(callback)
print(" Waiting for callback...")
time.sleep(1)
# ============================================================
# POOL WITH MULTIPLE ARGUMENTS
# ============================================================
def process_with_args(data, multiplier, offset=0):
"""Process with multiple arguments"""
time.sleep(0.05)
return data * multiplier + offset
print("\n4. Thread pool with multiple arguments:")
with ThreadPoolExecutor(max_workers=3) as executor:
# Submit with different arguments
futures = [
executor.submit(process_with_args, i, 10, offset=5)
for i in [1, 2, 3, 4, 5]
]
results = [f.result() for f in futures]
print(f" Results: {results}")
print("""
┌─────────────────────────────────────────────────────────────────┐
│ THREAD POOL ADVANTAGES: │
├─────────────────────────────────────────────────────────────────┤
│ • Reuses threads (efficient) │
│ • Limits concurrent threads │
│ • Easy task submission │
│ • Built-in result handling │
├─────────────────────────────────────────────────────────────────┤
│ THREAD POOL DISADVANTAGES: │
├─────────────────────────────────────────────────────────────────┤
│ • Less control over individual threads │
│ • Threads are shared │
│ • May block if all threads busy │
└─────────────────────────────────────────────────────────────────┘
""")
Thread pool key points:
- Reuse — threads are reused for multiple tasks
- Limited concurrency — max_workers controls
- Future objects — handle results asynchronously
- map() — easy parallel mapping
Quick Check: What is the benefit of using a thread pool? (Answer: Threads are reused, making it more efficient)
Daemon Threads
Background vs Foreground Threads
Daemon threads run in the background and automatically terminate when the main program exits. Non-daemon (foreground) threads keep the program running until they finish.
# ============================================================
# DAEMON THREADS
# ============================================================
import threading
import time
print("=" * 40)
print("DAEMON THREADS")
print("=" * 40)
# ============================================================
# NON-DAEMON THREAD
# ============================================================
def non_daemon_task():
"""Non-daemon thread that keeps running"""
print(" [Non-Daemon] Task starting...")
for i in range(3):
time.sleep(0.3)
print(f" [Non-Daemon] Working... {i+1}/3")
print(" [Non-Daemon] Task complete!")
print("\n1. Non-daemon thread:")
nd_thread = threading.Thread(target=non_daemon_task, name="NonDaemon")
nd_thread.start()
# ============================================================
# DAEMON THREAD
# ============================================================
def daemon_task():
"""Daemon thread that will be terminated"""
print(" [Daemon] Task starting...")
for i in range(5):
time.sleep(0.2)
print(f" [Daemon] Still running... {i+1}/5")
print(" [Daemon] Task complete! (may not execute)")
print("\n2. Daemon thread:")
d_thread = threading.Thread(target=daemon_task, name="Daemon")
d_thread.daemon = True # Set as daemon
d_thread.start()
# Wait for non-daemon to finish
time.sleep(1.5)
print("\nMain program ending...")
print("Daemon thread will be terminated automatically.")
# ============================================================
# CHECKING DAEMON STATUS
# ============================================================
def daemon_checker(name):
"""Check daemon status"""
t = threading.current_thread()
print(f" {name}: is_daemon = {t.daemon}")
print("\n3. Checking daemon status:")
# Create threads with different daemon settings
threads = [
threading.Thread(target=daemon_checker, args=("T1",), daemon=True),
threading.Thread(target=daemon_checker, args=("T2",), daemon=False),
]
for t in threads:
t.start()
for t in threads:
t.join()
# ============================================================
# USE CASE: LOGGING DAEMON
# ============================================================
class LoggingThread(threading.Thread):
"""Daemon thread for background logging"""
def __init__(self, log_file):
super().__init__(daemon=True)
self.log_file = log_file
self.running = True
def run(self):
count = 0
while self.running and count < 5:
time.sleep(0.5)
count += 1
with open(self.log_file, 'a') as f:
f.write(f"Log entry {count}\n")
print(f" [Logger] Written log {count}")
print(" [Logger] Stopping...")
print("\n4. Daemon logger thread:")
logger = LoggingThread("daemon.log")
logger.start()
time.sleep(2.5)
print("Main program ending, logger will terminate...")
print("""
┌─────────────────────────────────────────────────────────────────┐
│ DAEMON THREAD KEY POINTS: │
├─────────────────────────────────────────────────────────────────┤
│ • Background threads │
│ • Terminate when main program exits │
│ • Set with `thread.daemon = True` │
│ • Used for background tasks (logging, monitoring) │
│ • Use with caution (may exit mid-task) │
└─────────────────────────────────────────────────────────────────┘
""")
Daemon thread key points:
- Background — run in the background
- Auto-terminate — exit when main exits
- setDaemon() — mark as daemon
- Use cases — logging, monitoring, cleanup
Quick Check: What happens to a daemon thread when the main program exits? (Answer: It terminates automatically)
Thread Lifecycle
Understanding Thread States
A thread goes through several states during its lifetime: creation, ready, running, blocked, and terminated. Understanding these states helps you manage threads effectively.
# ============================================================
# THREAD LIFECYCLE DEMONSTRATION
# ============================================================
import threading
import time
import random
print("=" * 40)
print("THREAD LIFECYCLE")
print("=" * 40)
# ============================================================
# LIFECYCLE TRACKING
# ============================================================
class LifecycleThread(threading.Thread):
"""Thread that tracks its own lifecycle"""
def __init__(self, name):
super().__init__(name=name)
self.state = "created"
print(f" [{self.name}] State: {self.state}")
def run(self):
self.state = "running"
print(f" [{self.name}] State: {self.state}")
# Simulate work
duration = random.uniform(0.5, 1.5)
print(f" [{self.name}] Working for {duration:.2f}s...")
time.sleep(duration)
self.state = "finished"
print(f" [{self.name}] State: {self.state}")
print("\n1. Thread lifecycle tracking:")
t = LifecycleThread("LifecycleDemo")
print(f" Before start - alive: {t.is_alive()}")
t.start()
print(f" After start - alive: {t.is_alive()}")
t.join()
print(f" After join - alive: {t.is_alive()}")
# ============================================================
# BLOCKED STATE DEMONSTRATION
# ============================================================
lock = threading.Lock()
def thread_with_lock(thread_id):
"""Thread that demonstrates blocked state"""
print(f" Thread {thread_id}: Waiting for lock...")
with lock:
print(f" Thread {thread_id}: Lock acquired")
time.sleep(1)
print(f" Thread {thread_id}: Lock released")
print("\n2. Blocked state demonstration:")
# Start first thread (acquires lock)
t1 = threading.Thread(target=thread_with_lock, args=(1,))
t1.start()
# Give t1 time to acquire lock
time.sleep(0.1)
# Start second thread (will block)
t2 = threading.Thread(target=thread_with_lock, args=(2,))
t2.start()
t1.join()
t2.join()
# ============================================================
# STATE CHECKING
# ============================================================
def state_checker():
"""Check thread states"""
t = threading.current_thread()
print(f" {t.name}: alive={t.is_alive()}, daemon={t.daemon}")
print("\n3. State checking:")
t = threading.Thread(target=state_checker, name="StateChecker")
print(f" Before start: alive={t.is_alive()}")
t.start()
t.join()
print(f" After join: alive={t.is_alive()}")
print("""
┌─────────────────────────────────────────────────────────────────┐
│ THREAD LIFECYCLE STATES: │
├─────────────────────────────────────────────────────────────────┤
│ 1. CREATED - Thread object created but not started │
│ 2. READY - Thread is ready to run (start() called) │
│ 3. RUNNING - Thread is actively executing │
│ 4. BLOCKED - Thread is waiting (sleep, lock, I/O) │
│ 5. TERMINATED - Thread has finished execution │
├─────────────────────────────────────────────────────────────────┤
│ IMPORTANT METHODS: │
├─────────────────────────────────────────────────────────────────┤
│ • is_alive() - Check if thread is running │
│ • start() - Start thread execution │
│ • join() - Wait for thread to finish │
│ • name - Thread name property │
└─────────────────────────────────────────────────────────────────┘
""")
Thread lifecycle key points:
- Created — thread object exists
- Running — actively executing
- Blocked — waiting for something
- Terminated — finished execution
Quick Check: What method checks if a thread is still running? (Answer: is_alive())
Best Practices
Thread Creation Best Practices
# ============================================================
# THREAD CREATION BEST PRACTICES
# ============================================================
print("1. USE APPROPRIATE CREATION METHOD")
print(" - Function-based: Simple tasks, quick implementation")
print(" - Class-based: Complex threads with custom behavior")
print(" - Thread pools: Many similar tasks")
print("\n2. NAME YOUR THREADS")
print(" - Helps with debugging")
print(" - thread = Thread(..., name='MyThread')")
print(" - thread.name = 'Worker'")
print("\n3. HANDLE THREAD EXCEPTIONS")
print(" - Catch exceptions within thread")
print(" - Use try/except in target function")
print(" - Consider SafeThread class pattern")
print("\n4. USE JOIN() APPROPRIATELY")
print(" - Call join() to wait for thread completion")
print(" - Use timeout: join(timeout=5)")
print(" - Avoid deadlocks")
print("\n5. CONSIDER DAEMON THREADS")
print(" - Set daemon=True for background tasks")
print(" - Use daemon threads for logging, monitoring")
print(" - Don't use daemon for critical tasks")
print("\n6. LIMIT THREAD COUNT")
print(" - Too many threads = overhead")
print(" - Use thread pools for many tasks")
print(" - Monitor with active_count()")
print("\n7. AVOID SHARED STATE")
print(" - Minimize shared variables")
print(" - Use thread-local data when possible")
print(" - Use synchronization when needed")
print("\n8. TEST THOROUGHLY")
print(" - Race conditions are hard to find")
print(" - Test with different thread counts")
print(" - Use stress testing")
Best practices summary:
- Name threads — for debugging
- Handle exceptions — thread errors
- Use join() — wait for completion
- Limit threads — avoid overhead
Quick Check: Why should you name your threads? (Answer: For debugging and identification)
Try It Yourself
Experiment with thread creation in the editor below.
CREATING THREADS - PRACTICE
========================================
1. FUNCTION-BASED
----------------------------------------
Hello from Alice!
Goodbye from Alice!
2. CLASS-BASED
----------------------------------------
[Counter] Count: 1
[Counter] Count: 2
[Counter] Count: 3
3. MULTIPLE THREADS
----------------------------------------
Task 0: started
Task 1: started
Task 2: started
Task 0: done
Task 1: done
Task 2: done
All threads completed!
You've Got It!
You now understand how to create and manage threads in Python. You know function-based, class-based, thread pools, and daemon threads.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What is the difference between function-based and class-based thread creation?
What is the difference between start() and run()?
Why should I use a thread pool?
What is a common interview question about creating threads?
How many threads should I create?
Where to Go From Here
Now that you understand how to create threads, check out these related topics:
Introduction to Threads
Review the basics of threads and concurrency.
Review →Multi Tasking
Learn about multi-tasking with threads.
Learn More →Thread Synchronization
Learn how to synchronize threads safely.
Learn More →