Skip to content

Repository files navigation

Python Multithreading

A practical guide to multithreading in Python, demonstrating basic threading concepts, thread synchronization and thread-safe queue operations.

Overview

This repository contains three Python scripts that progressively introduce multithreading concepts:

  1. Basic Threading - Creating and managing multiple threads
  2. Thread Synchronization - Using locks to prevent race conditions
  3. Queue-based Threading - Producer-consumer pattern with thread-safe queues

Files Description

1. thread.py - Basic Multithreading

Concept: Introduction to creating and managing multiple threads.

This script demonstrates the fundamental concepts of threading in Python:

  • Creating Threads: Uses the Thread class to create 10 worker threads
  • Target Function: Each thread executes the square() function, which performs calculations in a loop
  • Starting Threads: All threads are started using the start() method
  • Joining Threads: The join() method ensures the main program waits for all threads to complete before finishing

Key Takeaway: Multiple threads can execute concurrently, potentially reducing execution time for I/O-bound or independent tasks.

# Basic pattern
thread = Thread(target=function_name)
thread.start()  # Begin execution
thread.join()   # Wait for completion

2. data_sharing.py - Thread Synchronization with Locks

Concept: Preventing race conditions when multiple threads access shared data.

This script illustrates the critical problem of race conditions and how to solve them:

  • The Problem: When multiple threads read and modify a shared variable (database_value), race conditions can occur
  • The Solution: Using a Lock object to ensure only one thread can modify the shared variable at a time
  • Two Approaches:
    • Manual: lock.acquire() and lock.release()
    • Context Manager: with lock: (recommended, automatically handles release)

Race Condition Example: Without locks, if two threads read database_value = 0 simultaneously, both increment to 1, and both write back 1 (instead of the expected 2).

Key Takeaway: Always use locks when multiple threads need to modify shared data to ensure thread safety.

# Pattern for thread-safe data access
with lock:
    # Critical section - only one thread at a time
    shared_variable = modify(shared_variable)

3. queue_in_thread.py - Thread-Safe Queue Operations

Concept: Producer-consumer pattern using Python's thread-safe Queue.

This script demonstrates a common multithreading pattern:

  • Worker Threads: 10 daemon threads continuously process items from a queue
  • Thread-Safe Queue: The Queue class handles all synchronization internally
  • Worker Pattern:
    • q.get() - Retrieves an item (blocks if queue is empty)
    • Process the item
    • q.task_done() - Signals completion of the item
  • Main Thread: Acts as producer, adding 20 items to the queue
  • Synchronization: q.join() blocks until all items are processed
  • Daemon Threads: Automatically terminate when the main program exits

Key Takeaway: Queues provide a thread-safe way to distribute work among multiple threads without manual locking.

# Producer-consumer pattern
q = Queue()

# Worker threads
def worker(q):
    while True:
        item = q.get()
        process(item)
        q.task_done()

# Producer (main thread)
q.put(item)
q.join()  # Wait for all items to be processed

Core Concepts

What is Multithreading?

Multithreading allows a program to execute multiple operations concurrently within a single process. Each thread runs independently but shares the same memory space.

Benefits:

  • Improved performance for I/O-bound operations
  • Better resource utilization
  • Responsive applications (UI remains active while background tasks run)

Challenges:

  • Race conditions when accessing shared data
  • Deadlocks if locks are not managed properly
  • Debugging complexity

Thread Synchronization

When multiple threads access shared resources, synchronization mechanisms are needed:

  • Lock: Mutual exclusion - only one thread can hold the lock at a time
  • Context Manager (with lock:): Ensures lock is always released, even if an exception occurs

Thread-Safe Queues

Python's Queue class provides built-in thread safety:

  • No need for manual locking
  • Blocks automatically when empty (get) or full (put)
  • Perfect for producer-consumer scenarios

Running the Examples

Each script can be run independently:

python thread.py
python data_sharing.py
python queue_in_thread.py

Requirements

  • Python 3.x
  • Standard library only (no external dependencies)

Learning Path

  1. Start with thread.py to understand basic thread creation
  2. Move to data_sharing.py to learn about race conditions and locks
  3. Finally, explore queue_in_thread.py for practical worker patterns

Best Practices

  1. Use locks for shared mutable data - Prevents race conditions
  2. Prefer context managers - with lock: is safer than manual acquire/release
  3. Use queues for work distribution - Simpler and safer than manual synchronization
  4. Make threads daemon when appropriate - They automatically terminate with the main program
  5. Always call task_done() - Required for queue.join() to work correctly

Additional Notes

  • Python's Global Interpreter Lock (GIL) means threads don't provide true parallelism for CPU-bound tasks
  • For CPU-intensive work, consider using multiprocessing instead
  • Threads are ideal for I/O-bound operations (network requests, file operations, database queries)

License

  • This is my example code for learning purposes. Use freely.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages