Python threading Module

The threading module in Python provides a way to create and manage multiple threads, enabling parallel execution of code. This is particularly useful for I/O-bound and high-level structured network code.

Table of Contents

  1. Introduction
  2. Key Classes and Functions
    • Thread
    • Lock
    • RLock
    • Condition
    • Semaphore
    • Event
    • Timer
    • Barrier
    • active_count()
    • current_thread()
    • main_thread()
    • enumerate()
  3. Examples
    • Creating and Starting Threads
    • Using Locks
    • Using Condition Variables
    • Using Semaphores
    • Using Events
    • Using Timers
    • Using Barriers
  4. Real-World Use Case
  5. Conclusion
  6. References

Introduction

The threading module provides a higher-level interface for working with threads in Python. It includes several classes and functions to manage threads and synchronize their execution.

Key Classes and Functions

Thread

Represents a thread of execution.

import threading

def worker():
    print("Worker thread")

thread = threading.Thread(target=worker)
thread.start()
thread.join()

Output:

Worker thread

Lock

A lock object is a synchronization primitive.

import threading

lock = threading.Lock()
lock.acquire()
try:
    # Critical section
    pass
finally:
    lock.release()

RLock

A reentrant lock is a synchronization primitive that may be acquired multiple times by the same thread.

import threading

rlock = threading.RLock()
rlock.acquire()
try:
    # Critical section
    pass
finally:
    rlock.release()

Condition

A condition variable allows one or more threads to wait until they are notified by another thread.

import threading

condition = threading.Condition()

def consumer():
    with condition:
        condition.wait()
        print("Consumer notified")

def producer():
    with condition:
        print("Producer notifying")
        condition.notify_all()

threading.Thread(target=consumer).start()
threading.Thread(target=producer).start()

Output:

Producer notifying
Consumer notified

Semaphore

A semaphore is a synchronization primitive that manages an internal counter.

import threading

semaphore = threading.Semaphore(2)

def worker():
    with semaphore:
        print("Worker acquired semaphore")
        # Critical section

for _ in range(4):
    threading.Thread(target=worker).start()

Output:

Worker acquired semaphore
Worker acquired semaphore
Worker acquired semaphore
Worker acquired semaphore

Event

An event object manages an internal flag that threads can wait for.

import threading

event = threading.Event()

def worker():
    event.wait()
    print("Worker activated")

threading.Thread(target=worker).start()
print("Main thread setting event")
event.set()

Output:

Main thread setting event
Worker activated

Timer

A timer thread executes a function after a specified interval.

import threading

def timeout():
    print("Timeout reached")

timer = threading.Timer(2.0, timeout)
timer.start()

Output:

Timeout reached

Barrier

A barrier is a synchronization primitive that allows multiple threads to wait until they have all reached a certain point.

import threading

barrier = threading.Barrier(3)

def worker():
    print("Worker waiting at barrier")
    barrier.wait()
    print("Worker passed barrier")

for _ in range(3):
    threading.Thread(target=worker).start()

Output:

Worker waiting at barrier
Worker waiting at barrier
Worker waiting at barrier
Worker passed barrier
Worker passed barrier
Worker passed barrier

active_count()

Returns the number of currently active threads.

import threading

print(f'Active threads: {threading.active_count()}')

Output:

Active threads: 1

current_thread()

Returns the current thread object.

import threading

print(f'Current thread: {threading.current_thread().name}')

Output:

Current thread: MainThread

main_thread()

Returns the main thread object.

import threading

print(f'Main thread: {threading.main_thread().name}')

Output:

Main thread: MainThread

enumerate()

Returns a list of all currently active thread objects.

import threading

threads = threading.enumerate()
print(f'All active threads: {[thread.name for thread in threads]}')

Output:

All active threads: ['MainThread']

Examples

Creating and Starting Threads

import threading

def worker():
    print(f'Worker: {threading.current_thread().name}')

threads = []
for i in range(5):
    thread = threading.Thread(target=worker, name=f'Thread-{i}')
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

Output:

Worker: Thread-0
Worker: Thread-1
Worker: Thread-2
Worker: Thread-3
Worker: Thread-4

Using Locks

import threading

lock = threading.Lock()
counter = 0

def increment():
    global counter
    with lock:
        local_counter = counter
        local_counter += 1
        counter = local_counter
        print(f'Counter: {counter}')

threads = []
for _ in range(10):
    thread = threading.Thread(target=increment)
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

Output:

Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
Counter: 6
Counter: 7
Counter: 8
Counter: 9
Counter: 10

Using Condition Variables

import threading

condition = threading.Condition()
items = []

def consumer():
    with condition:
        while not items:
            condition.wait()
        item = items.pop(0)
        print(f'Consumer got: {item}')

def producer():
    with condition:
        items.append('item')
        print('Producer added item')
        condition.notify()

threading.Thread(target=consumer).start()
threading.Thread(target=producer).start()

Output:

Producer added item
Consumer got: item

Using Semaphores

import threading

semaphore = threading.Semaphore(2)

def worker():
    with semaphore:
        print(f'{threading.current_thread().name} acquired semaphore')
        import time
        time.sleep(1)

threads = []
for i in range(4):
    thread = threading.Thread(target=worker, name=f'Worker-{i}')
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

Output:

Worker-0 acquired semaphore
Worker-1 acquired semaphore
Worker-2 acquired semaphore
Worker-3 acquired semaphore

Using Events

import threading

event = threading.Event()

def worker():
    print(f'{threading.current_thread().name} waiting for event')
    event.wait()
    print(f'{threading.current_thread().name} event triggered')

thread = threading.Thread(target=worker, name='Worker')
thread.start()

import time
time.sleep(2)
print('Main thread setting event')
event.set()

Output:

Worker waiting for event
Main thread setting event
Worker event triggered

Using Timers

import threading

def timeout():
    print('Timeout reached')

timer = threading.Timer(2.0, timeout)
timer.start()
timer.join()

Output:

Timeout reached

Using Barriers

import threading

barrier = threading.Barrier(3)

def worker():
    print(f'{threading.current_thread().name} waiting at barrier')
    barrier.wait()
    print(f'{threading.current_thread().name} passed barrier')

threads = []
for i in range(3):
    thread = threading.Thread(target=worker, name=f'Worker-{i}')
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

Output:

Worker-0 waiting at barrier
Worker-1 waiting at barrier
Worker-2 waiting at barrier
Worker-2 passed barrier
Worker-0 passed barrier
Worker-1 passed barrier

Real-World Use Case

Web Scraping with Multiple Threads

import threading
import requests

urls = [
    'http://example.com',
    'http://example.org',
    'http://example.net',
]

def fetch(url):
    response = requests.get(url)
    print(f'Fetched {url} with status {response.status_code}')

threads = []
for url in urls:
    thread = threading.Thread(target=fetch, args=(url,))
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

Output:

Fetched http://example.org with status 200
Fetched http://example.com with status 200
Fetched http://example.net with status 200

Conclusion

The threading The thread module in Python provides a robust and flexible way to work with threads. Threads allow you to execute multiple operations concurrently, which is particularly useful for I/O-bound and network-bound tasks. The module includes various synchronization primitives such as locks, conditions, semaphores, events, and barriers, allowing for effective thread management and coordination.

References

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top