Python threading module

Python’sthreadingmodule is one of the standard libraries used to implement multithreaded programming. Multithreading allows a program to execute multiple tasks at the same time, thereby improving program efficiency and responsiveness.

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

What is a thread?

You can think of threads as employees in an office:

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

  • A multithreaded program is like having multiple employees, who cansimultaneouslyperform different tasks, greatly improving work efficiency.

In computer science:

  • Process: A running program with independent memory space (for example, the browser and music player you have open at the same time are two processes).
  • Thread: An independent execution flow within a process, and the basic unit of CPU scheduling. All threads in the same processshare the process’s memory space(such as global variables).

Why use multithreading?

In a single-threaded program, tasks are executed one after another. If a task needs to wait (e.g., waiting for a network response or file reading), the entire program is blocked until the task completes. Multithreading allows the program to continue executing other tasks while waiting for one task, thus improving overall program performance.

Python threads and the Global Interpreter Lock (GIL)

Python has a mechanism called the Global Interpreter Lock (GIL), which 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, multithreading usually cannot take advantage of multiple cores to speed up computation, and may even become slower due to thread switching overhead.

So, where is Python multithreading useful?For I/O-intensive tasks (such as network requests, file reading/writing, 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 you wait for a webpage response, the program can handle another task.


How to use the threading module?

To usethreadingthe module, the first step is to import it:

import threading
import time  # 用于模拟耗时操作

The most basic way to create a thread is to usethreading.Threadthe class.

Syntax explanation:

thread_obj = threading.Thread(target=函数名, args=(参数元组,))
  • target: Specifies the function to be executed after the thread starts.
  • args: The argument passed to the target function must be a tuple. If there is only one argument, it must be written in(parameter,)the form `(arg,)`.

1. Creating threads

In Python, you can, by inheriting thethreading.Threadclass or by directly using thethreading.Threadconstructor, create a thread.

Method 1: Inheritancethreading.ThreadClass

Example

import threading

class MyThread(threading.Thread):
    def run(self):
        print("Thread starts executing")
        # Write the code to be executed by the thread here
        print("Thread execution finished")

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

Method 2: Using thethreading.Threadconstructor.

Example

import threading

def my_function():
    print("Thread starts executing")
    # Write the code to be executed by the thread here
    print("Thread execution finished")

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

2. Thread synchronization

In multithreaded programming, multiple threads may access shared resources at the same time, which can lead to data inconsistency problems. To avoid this, thread synchronization mechanisms such as locks (Lock)。

Example

import threading

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

def my_function():
    with lock:
        print("Thread starts executing")
        # Write the code to be executed by the thread here
        print("Thread execution finished")

# Create a thread instance
thread1 = threading.Thread(target=my_function)
thread2 = threading.Thread(target=my_function)
# Start the thread
thread1.start()
thread2.start()
# Wait for the thread to complete
thread1.join()
thread2.join()
print("Main thread finished")

3. Inter-thread communication

Inter-thread communication can be implemented through a queue (Queue).Queueis thread-safe and can safely pass data between multiple threads.

Example

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 populate 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")

Common classes, methods, and attributes

1. Core classes

Class/Method/AttributeDescriptionExample
threading.ThreadThread class, used to create and manage threadst = Thread(target=func, args=(1,))
threading.LockMutex lock (primitive lock)lock = Lock()
threading.RLockReentrant lock (can be acquired multiple times by the same thread)rlock = RLock()
threading.EventEvent object, used for thread synchronizationevent = Event()
threading.ConditionCondition variable, used for complex thread coordinationcond = Condition()
threading.SemaphoreSemaphore, controls the number of concurrent threadssem = Semaphore(3)
threading.BoundedSemaphoreBounded semaphore (prevents the count from exceeding the initial value)b_sem = BoundedSemaphore(2)
threading.TimerTimer thread, delayed executiontimer = Timer(5.0, func)
threading.localThread-local data (stored independently for each thread)local_data = threading.local()

2. Common methods/attributes of Thread objects

Method/AttributeDescriptionExample
start()Start the threadt.start()
run()Method executed by the thread (can be overridden)Override this method when defining a custom class
join(timeout=None)Blocks the current thread until the target thread finishest.join()
is_alive()Check whether the thread is runningif t.is_alive():
nameThread name (can be modified)t.name = "Worker-1"
daemonDaemon thread flag (automatically ends when the main thread exits)t.daemon = True
identThread identifier (`None` before the thread is started)None)print(t.ident)

3. Common methods of Lock/RLock

MethodDescriptionExample
acquire(blocking=True, timeout=-1)Acquire the lock (blocking or non-blocking)lock.acquire()
release()Release the locklock.release()
locked()Check whether the lock is heldif not lock.locked():

4. Common methods of Event

MethodDescriptionExample
set()Set the event to true and wake up all waiting threadsevent.set()
clear()Reset the event to falseevent.clear()
wait(timeout=None)Block until the event is true or a timeout occursevent.wait(2.0)
is_set()Check the event statusif event.is_set():

5. Common methods of Condition

MethodDescriptionExample
wait(timeout=None)Release the lock and block until notified or a timeout occurscond.wait()
notify(n=1)Wake up at mostnwaiting threadscond.notify(2)
notify_all()Wake up all waiting threadscond.notify_all()

6. Module-level functions/attributes

Function/AttributeDescriptionExample
threading.active_count()Return the number of currently active threadsprint(threading.active_count())
threading.current_thread()Return the current thread objectprint(threading.current_thread().name)
threading.enumerate()Return a list of all active threadsfor t in threading.enumerate():
threading.main_thread()Return the main thread objectif threading.current_thread() is threading.main_thread():
threading.get_ident()Return the current thread’s identifier (Python 3.3+)print(threading.get_ident())

Example

1. Basic Thread Creation

Example

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

Example

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

Example

event = threading.Event()

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

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

# The main thread triggers the event
threading.Event().wait(2.0)  # Simulated delay
event.set()
t.join()

4. Producer-Consumer Model (Condition)

Example

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()

Notes

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

  2. Thread Safety: In a multithreaded environment, ensure that access to shared resources is thread-safe, avoiding 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.


Quiz

Threading module knowledge quiz

Question 1
1. Python's Global Interpreter Lock (GIL) mainly affects the performance of which type of multithreaded task?
Question 2
2. After creating a thread object, which method should be called to start the thread's execution?
Question 3
3. When multiple threads need to modify the same global variable, what should be used to avoid data corruption?
Question 4
4. What is the purpose of the main thread using thread.join()?
More Extensions