Skip to content

Python threading Module

Python’s threading module is one of the standard libraries for implementing multi-threaded programming. Multi-threading allows a program to execute multiple tasks at the same time, improving the program’s efficiency and responsiveness.

The threading module provides tools for creating and managing threads, allowing developers to easily write concurrent programs.

You can think of a thread as an employee in an office:

  • A single-threaded program is like having only one employee who must complete all tasks sequentially — printing documents, replying to emails, making coffee, etc.

  • A multi-threaded program is like having multiple employees who can simultaneously work on different tasks, greatly improving work efficiency.

In computer science:

  • Process: A running program with its own independent memory space (e.g., your browser and music player running simultaneously are two processes).
  • Thread: An independent execution flow within a process, and the basic unit of CPU scheduling. All threads within the same process share the process’s memory space (such as global variables).

In single-threaded programs, tasks are executed sequentially one after another. If a task needs to wait (e.g., waiting for a network response or file read), the entire program is blocked until that task completes. Multi-threading allows the program to continue executing other tasks while waiting for one task, thereby improving the overall performance of the program.

Python Threads and the Global Interpreter Lock (GIL)

Section titled “Python Threads and the Global Interpreter Lock (GIL)”

Python has a mechanism called the Global Interpreter Lock (GIL). The GIL ensures that only one thread can execute Python bytecode at any given time.

What does this mean? For CPU-intensive tasks (such as scientific computing, image processing), due to the GIL, multi-threading typically cannot take advantage of multi-core benefits to improve computation speed, and may even become slower due to the overhead of thread switching.

So, where does Python multi-threading shine? For I/O-intensive tasks (such as network requests, reading/writing files, waiting for user input), threads release the GIL while waiting for I/O operations to complete, allowing other threads to run. This can significantly improve the overall responsiveness and efficiency of the program, because while waiting for a web page response, the program can handle another task.


The first step in using the threading module is to import it:

import threading
import time  # Used to simulate time-consuming operations

The most basic way to create a thread is to use the threading.Thread class.

Syntax Description:

thread_obj = threading.Thread(target=function_name, args=(argument_tuple,))
  • target: Specifies the function to execute after the thread starts.
  • args: Arguments passed to the target function, which must be a tuple type. If there is only one argument, it needs to be written as (arg,).

In Python, you can create threads by inheriting the threading.Thread class or directly using the threading.Thread constructor.

Method 1: Inheriting the threading.Thread Class

Section titled “Method 1: Inheriting the threading.Thread Class”
import threading

class MyThread(threading.Thread):
    def run(self):
        print("Thread started executing")
        # Write the code the thread will execute here
        print("Thread finished executing")

# Create a thread instance
thread = MyThread()
# Start the thread
thread.start()
# Wait for the thread to finish
thread.join()
print("Main thread ended")

Method 2: Using the threading.Thread Constructor

Section titled “Method 2: Using the threading.Thread Constructor”
import threading

def my_function():
    print("Thread started executing")
    # Write the code the thread will execute here
    print("Thread finished executing")

# Create a thread instance
thread = threading.Thread(target=my_function)
# Start the thread
thread.start()
# Wait for the thread to finish
thread.join()
print("Main thread ended")

In multi-threaded programming, multiple threads may simultaneously access shared resources, which can lead to data inconsistency issues. To avoid this, you can use thread synchronization mechanisms, such as locks (Lock).

import threading

# Create a lock object
lock = threading.Lock()

def my_function():
    with lock:
        print("Thread started executing")
        # Write the code the thread will execute here
        print("Thread finished executing")

# Create thread instances
thread1 = threading.Thread(target=my_function)
thread2 = threading.Thread(target=my_function)
# Start the threads
thread1.start()
thread2.start()
# Wait for the threads to finish
thread1.join()
thread2.join()
print("Main thread ended")

Inter-thread communication can be achieved through queues (Queue). Queue is thread-safe and can safely pass data between multiple threads.

import threading
import queue

def worker(q):
    while not q.empty():
        item = q.get()
        print(f"Processing item: {item}")
        q.task_done()

# Create a queue and fill it with data
q = queue.Queue()
for i in range(10):
    q.put(i)

# Create thread instances
thread1 = threading.Thread(target=worker, args=(q,))
thread2 = threading.Thread(target=worker, args=(q,))
# Start the threads
thread1.start()
thread2.start()
# Wait for all items in the queue to be processed
q.join()
print("All items processed")

Class/Method/Attribute Description Example
threading.Thread Thread class for creating and managing threads t = Thread(target=func, args=(1,))
threading.Lock Mutex lock (primitive lock) lock = Lock()
threading.RLock Reentrant lock (same thread can acquire multiple times) rlock = RLock()
threading.Event Event object for thread synchronization event = Event()
threading.Condition Condition variable for complex thread coordination cond = Condition()
threading.Semaphore Semaphore for controlling concurrent thread count sem = Semaphore(3)
threading.BoundedSemaphore Bounded semaphore (prevents count from exceeding initial value) b_sem = BoundedSemaphore(2)
threading.Timer Timer thread for delayed execution timer = Timer(5.0, func)
threading.local Thread-local data (independent storage per thread) local_data = threading.local()

2. Common Methods/Attributes of the Thread Object

Section titled “2. Common Methods/Attributes of the Thread Object”
Method/Attribute Description Example
start() Start the thread t.start()
run() Method executed by the thread (can be overridden) Override in custom class
join(timeout=None) Block the current thread until the target thread ends t.join()
is_alive() Check if the thread is running if t.is_alive():
name Thread name (modifiable) t.name = "Worker-1"
daemon Daemon thread flag (auto-exits when main thread exits) t.daemon = True
ident Thread identifier (None if not started) print(t.ident)
Method Description Example
acquire(blocking=True, timeout=-1) Acquire the lock (blocking or non-blocking) lock.acquire()
release() Release the lock lock.release()
locked() Check if the lock is held if not lock.locked():
Method Description Example
set() Set the event to true, waking all waiting threads event.set()
clear() Reset the event to false event.clear()
wait(timeout=None) Block until the event is true or timeout event.wait(2.0)
is_set() Check the event status if event.is_set():
Method Description Example
wait(timeout=None) Release the lock and block until notified or timeout cond.wait()
notify(n=1) Wake up to n waiting threads cond.notify(2)
notify_all() Wake up all waiting threads cond.notify_all()
Function/Attribute Description Example
threading.active_count() Return the number of currently active threads print(threading.active_count())
threading.current_thread() Return the current thread object print(threading.current_thread().name)
threading.enumerate() Return a list of all active threads for t in threading.enumerate():
threading.main_thread() Return the main thread object if threading.current_thread() is threading.main_thread():
threading.get_ident() Return the current thread’s identifier (Python 3.3+) print(threading.get_ident())

1. Basic Thread Creation

import threading

def worker(num):
    print(f"Worker {num} started")

threads = []
for i in range(3):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

2. Using Locks to Protect Shared Resources

lock = threading.Lock()
count = 0

def increment():
    global count
    with lock:  # Automatically acquire and release the lock
        count += 1

threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(count)  # Output: 10

3. Event Synchronization

event = threading.Event()

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

t = threading.Thread(target=waiter)
t.start()

# Main thread triggers the event
threading.Event().wait(2.0)  # Simulate delay
event.set()
t.join()

4. Producer-Consumer Model (Condition)

import random
from threading import Condition

queue = []
cond = Condition()
MAX_ITEMS = 5

def producer():
    for _ in range(10):
        with cond:
            while len(queue) >= MAX_ITEMS:
                cond.wait()
            item = random.randint(1, 100)
            queue.append(item)
            print(f"Produced {item}")
            cond.notify()

def consumer():
    for _ in range(10):
        with cond:
            while not queue:
                cond.wait()
            item = queue.pop(0)
            print(f"Consumed {item}")
            cond.notify()

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

  1. Global Interpreter Lock (GIL): Python’s GIL restricts only one thread from executing Python bytecode at a time. Therefore, in CPU-intensive tasks, multi-threading may not bring performance improvements. For I/O-intensive tasks, multi-threading is still beneficial.

  2. Thread Safety: In a multi-threaded environment, ensure that access to shared resources is thread-safe to avoid data races and deadlocks.

  3. Thread Count: Creating too many threads may exhaust system resources and affect program performance. Control the number of threads reasonably, or use a thread pool (ThreadPoolExecutor) to manage threads.