threading

Updated March 13, 2026 · Modules
threading concurrency parallelism multithreading stdlib

The threading module implements higher-level threading interfaces on top of the lower-level _thread module. It provides a Thread class for creating and managing threads, synchronization primitives like Lock, RLock, Condition, Semaphore, Event, and Barrier, and utility functions for working with threads.

Syntax

import threading

Core Classes

Thread

The Thread class represents a thread of execution. The most common way to use it is to subclass Thread and override the run() method, or to pass a callable target to the constructor.

import threading

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

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

Lock

A Lock is a synchronization primitive that can be acquired by only one thread at a time. It ensures exclusive access to a shared resource.

lock = threading.Lock()

def safe_increment():
    with lock:
        # critical section
        global counter
        counter += 1

RLock

A reentrant lock (RLock) can be acquired multiple times by the same thread without deadlocking. This is useful for recursive functions that need to lock the same resource.

rlock = threading.RLock()

def recursive_func(n):
    with rlock:
        if n > 0:
            recursive_func(n - 1)

Condition

A Condition variable allows one or more threads to wait until they are notified by another thread. It is associated with a lock.

condition = threading.Condition()
queue = []

def consumer():
    with condition:
        while not queue:
            condition.wait()
        item = queue.pop()
        print(f"Consumed {item}")

def producer(item):
    with condition:
        queue.append(item)
        condition.notify()

Semaphore

A Semaphore manages a counter that limits the number of threads that can access a resource concurrently.

semaphore = threading.Semaphore(3)  # allow up to 3 threads

def limited_access():
    with semaphore:
        # do work
        pass

Event

An Event is a simple synchronization object that allows one thread to signal an event that other threads are waiting for.

event = threading.Event()

def waiter():
    print("Waiting for event")
    event.wait()
    print("Event set")

def setter():
    time.sleep(2)
    event.set()

Barrier

A Barrier ensures a fixed number of threads wait for each other before proceeding past a certain point.

barrier = threading.Barrier(3)

def party():
    print("Waiting at barrier")
    barrier.wait()
    print("Barrier crossed")

Common Patterns

Daemon Threads

Daemon threads are terminated when the main program exits, regardless of whether they are finished. Set daemon=True before starting the thread.

thread = threading.Thread(target=worker, daemon=True)
thread.start()
# main program can exit without waiting for thread

Thread‑Local Data

threading.local() creates thread‑local storage where each thread sees its own copy of the data.

local_data = threading.local()

def set_name(name):
    local_data.name = name

def get_name():
    return getattr(local_data, "name", "unknown")

Thread Pools Using ThreadPoolExecutor

Although threading does not provide a built‑in thread pool, the concurrent.futures.ThreadPoolExecutor offers a high‑level interface for managing a pool of worker threads.

from concurrent.futures import ThreadPoolExecutor

def task(n):
    return n * n

with ThreadPoolExecutor(max_workers=4) as executor:
    results = executor.map(task, range(10))

Errors

Common errors when using the threading module:

  • RuntimeError: cannot join current thread – attempting to join the thread from within itself.
  • RuntimeError: threads can only be started once – calling start() on an already‑started thread.
  • Deadlocks – when two or more threads wait for each other to release locks.
  • Race conditions – unsynchronized access to shared mutable state leading to unpredictable behavior.

See Also