Python Threads: Concepts and Implementation Examples

1. Thread Introduction

Threads are the execution units within a process (a process allocates memory/resources, while threads execute code). Unlike processes, threads do not require new memory space or code duplication, making them lighter and faster to create.

Why Use Threads?

  • Threads are cheaper than processes (no memory allocation or code copying).
  • Enable concurrent execution of tasks within a single process.

Thread Types

  • User-Level Threads: Managed by user-space code (no kernel involvement). Efficient but cannot utilize multi-core CPUs.
  • Kernel-Level Threads: Managed by the OS kernel. Can utilize multi-core CPUs (e.g., Windows threads) but involve kernel-mode switches.

2. Implementing Threads in Python

Use the threading module (similar to multiprocessing for processes).

The threading Module

threading.Thread Class

Create threads via two methods: target function or subclassing Thread.

1. Creating Threads

Method 1: Target Function
from threading import Thread

def worker(name):
    print(f"{name} is running")

# Create and start thread
t = Thread(target=worker, args=('Alice',))
t.start()
print("Main thread")

Output: Alice is runningMain thread (threads start quickly).

Method 2: Subclassing Thread
from threading import Thread

class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.worker_name = name

    def run(self):
        print(f"{self.worker_name} is running")

# Create and start thread
t = MyThread('Bob')
t.start()

Output: Bob is running

TCP Server with Threads

Handle multiple clients concurrently using threads:

Server Code

from threading import Thread
import socket

# Create socket
s = socket.socket()
s.bind(('127.0.0.1', 9999))
s.listen(5)

def handle_client(client_sock):
    while True:
        data = client_sock.recv(1024)
        print(data.decode('utf8'))
        client_sock.send('Server response'.encode('utf8'))

# Accept clients and start threads
while True:
    client, addr = s.accept()
    t = Thread(target=handle_client, args=(client,))
    t.start()

Client Code

import socket

client = socket.socket()
client.connect(('127.0.0.1', 9999))

while True:
    msg = input('>>> ').strip()
    client.send(msg.encode())
    response = client.recv(1024)
    print(response.decode())

Thread Join Method

The join() method makes the main thread wait for a child thread to finish:

from threading import Thread
import time

def worker(name):
    time.sleep(2)
    print(f"{name} is running")

t = Thread(target=worker, args=('Dave',))
t.start()
t.join()  # Main thread waits here
print("Main thread continues")

Output: Dave is runningMain thread continues

Shared Data Between Threads

Threads in the same process share memory:

from threading import Thread

username = 'Eve'

def worker():
    global username
    username = 'Eve_modified'
    print(f"{username} (thread)")

t = Thread(target=worker)
t.start()
t.join()
print(f"{username} (main thread)")

Output: Eve_modified (thread)Eve_modified (main thread)

Thread Object Properties/Methods

1. Same Process Check

Threads share the same process (verify via os.getpid()):

from threading import Thread
import os

def worker():
    print(f"Child thread PID: {os.getpid()}")

t = Thread(target=worker)
t.start()
t.join()
print(f"Main thread PID: {os.getpid()}")

2. Active Thread Count

Use threading.active_count() (includes the main thread):

from threading import Thread, active_count
import time

def worker(name):
    time.sleep(1)
    print(f"{name} is running")

t = Thread(target=worker, args=('Frank',))
t.start()
print(active_count())  # Output: 2 (main + child)
print("Main thread")

3. Thread Name

Get the thread name via current_thread().name or self.name (subclass):

from threading import Thread, current_thread
import time

def worker():
    time.sleep(1)
    print(current_thread().name)

t = Thread(target=worker)
t.start()
print("Main thread", current_thread().name)

4. Is Thread Alive?

Check if a thread is running with is_alive():

from threading import Thread
import time

def worker(name):
    print(f"{name} is running")
    time.sleep(2)
    print(f"{name} is over")

t = Thread(target=worker, args=('Grace',))
t.start()
print(t.is_alive())  # Output: True
print("Main thread")

GIL (Global Interpreter Lock)

In CPython, the GIL is a mutex that allows only one thread to execute Python bytecode at a time. This limits multi-core utilization (due to non-thread-safe memory management). For CPU-bound tasks, use multiprocessing; for I/O-bound tasks, threads (via multitasking) are efficieent.

Deadlock

A deadlock occurs when threads wait for each other’s locks indefinitely:

from threading import Thread, Lock
import time

lock_a = Lock()
lock_b = Lock()

class DeadlockThread(Thread):
    def run(self):
        self.acquire_locks()
        self.release_locks()

    def acquire_locks(self):
        lock_a.acquire()
        print(f"{self.name} acquired Lock A")
        lock_b.acquire()
        print(f"{self.name} acquired Lock B")

    def release_locks(self):
        lock_b.release()
        lock_a.release()

for _ in range(2):
    t = DeadlockThread()
    t.start()

Semaphore

A semaphore manages multiple locks (e.g., limit concurrent threads):

from threading import Thread, Semaphore
import time
import random

sem = Semaphore(3)  # Allow 3 concurrent threads

def task(name):
    with sem:  # Acquire/release automatically
        print(f"{name} is working")
        time.sleep(random.randint(1, 2))
        print(f"{name} finished")

for i in range(10):
    t = Thread(target=task, args=(f"Task-{i}",))
    t.start()

Event

An Event synchronizes threads (e.g., a traffic light):

from threading import Thread, Event
import time

traffic_light = Event()

def light():
    print("Red light!")
    time.sleep(2)
    print("Green light!")
    traffic_light.set()  # Wake waiting threads

def car(name):
    print(f"{name} waiting for green...")
    traffic_light.wait()  # Block until event is set
    print(f"{name} drives!")

# Start traffic light thread
t_light = Thread(target=light)
t_light.start()

# Start car threads
for i in range(5):
    t_car = Thread(target=car, args=(f"Car-{i}",))
    t_car.start()

Thread/Process Pools

Pools limit concurrent threads/processes (reuse resources):

Thread Pool

from concurrent.futures import ThreadPoolExecutor
from threading import current_thread
import time

# Create a thread pool with 3 threads
pool = ThreadPoolExecutor(max_workers=3)

def task(x):
    time.sleep(1)
    print(f"Task {x} (Thread: {current_thread().name})")
    return x * 2

# Submit tasks (asynchronous)
for i in range(5):
    future = pool.submit(task, i)
    print(future.result())  # Wait for result (synchronous)

Process Pool

from concurrent.futures import ProcessPoolExecutor
import os
import time

# Create a process pool with 2 processes
pool = ProcessPoolExecutor(max_workers=2)

def process_task(x):
    time.sleep(1)
    print(f"Task {x} (PID: {os.getpid()})")
    return x * 2

if __name__ == "__main__":
    for i in range(5):
        future = pool.submit(process_task, i)
        print(future.result())  # Wait for result (synchronous)

Tags: python threads threading module Concurrency GIL

Posted on Sun, 10 May 2026 14:38:52 +0000 by goodgeneguo