Python threading 使用指南:并发编程的轻骑兵

Python threading 使用指南:并发编程的轻骑兵

作者:书到用时方恨少!

发布日期:2026年4月2日

阅读时长:约19分钟

📌 前言

在 Python 并发编程的世界里,threading 模块如同一匹轻骑兵------轻量、灵活、响应迅速。它允许你在单个进程中创建多个线程,共享同一块内存空间,特别适合 I/O 密集型任务 (如网络爬虫、文件读写、数据库交互)。然而,由于全局解释器锁(GIL)的存在,threading 并不能让 CPU 密集型任务真正并行,但这并不妨碍它成为提升程序响应速度和资源利用率的利器。

无论你是想编写一个不卡顿的 GUI 程序,还是需要并发处理成千上万个网络请求,这篇博客都将带你从零开始,深入理解 threading 的核心概念、同步机制、通信方式以及最佳实践。让我们一同探索并发编程的奥秘!


1. 🧵 线程是什么?为什么需要线程?

1.1 进程与线程

  • 进程:操作系统资源分配的基本单位,拥有独立的内存空间、文件句柄等。进程间相互隔离,通信成本高。
  • 线程:CPU 调度的基本单位,隶属于进程。同一个进程内的线程共享内存空间和资源,创建和切换成本远低于进程。

可以这样理解:进程就像一家公司,线程就是公司里的员工。员工共享公司的办公区、设备,可以高效协作;而不同公司之间的资源不共享,沟通需要额外的渠道(如邮件)。

1.2 GIL(全局解释器锁)------ Python 线程的"紧箍咒"

CPython(官方 Python 实现)中有一个 GIL,它确保同一时刻只有一个线程执行 Python 字节码。这意味着即使在多核 CPU 上,Python 的多线程也无法真正并行执行计算密集型任务。

但这是否意味着 threading 毫无用处? 绝对不是!

  • I/O 密集型任务:当线程执行 I/O 操作(如网络请求、文件读写、用户输入)时,会释放 GIL,其他线程可以趁机运行。因此多线程可以极大地提高 I/O 密集型程序的吞吐量。
  • 响应性:即使有一个线程在计算,另一个线程也可以处理用户界面事件,防止程序"假死"。

选型小贴士

  • I/O 密集型 → 优先考虑 threadingasyncio
  • CPU 密集型 → 使用 multiprocessing
  • 高并发网络服务 → asyncio 可能更高效

2. 🚀 快速入门:创建并启动线程

threading 模块提供了 Thread 类,你可以通过两种方式创建线程。

2.1 方式一:传入目标函数

python 复制代码
import threading
import time

def worker(name, delay):
    print(f"线程 {name} 开始")
    time.sleep(delay)
    print(f"线程 {name} 结束")

# 创建线程
t1 = threading.Thread(target=worker, args=("A", 2))
t2 = threading.Thread(target=worker, args=("B", 1))

# 启动线程
t1.start()
t2.start()

# 等待线程结束
t1.join()
t2.join()

print("所有线程执行完毕")
  • target:可调用对象(函数)。
  • args:位置参数元组。
  • kwargs:关键字参数字典。
  • start():启动线程(实际开始运行)。
  • join([timeout]):等待线程结束(阻塞主线程)。

2.2 方式二:继承 Thread 类

python 复制代码
import threading
import time

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

    def run(self):   # 重写 run 方法
        print(f"线程 {self.name} 开始")
        time.sleep(self.delay)
        print(f"线程 {self.name} 结束")

t1 = MyThread("A", 2)
t2 = MyThread("B", 1)
t1.start()
t2.start()
t1.join()
t2.join()

2.3 守护线程(Daemon Thread)

守护线程在主线程结束时自动终止(不等待其完成)。设置 daemon=True 即可。

python 复制代码
def daemon_task():
    while True:
        print("守护线程运行中...")
        time.sleep(1)

d = threading.Thread(target=daemon_task, daemon=True)
d.start()
time.sleep(3)
print("主线程结束,守护线程也随之结束")

3. 🤝 线程同步:防止数据混乱

多个线程共享内存,同时修改同一变量会导致数据竞争(race condition)。threading 提供了多种同步原语。

3.1 Lock(互斥锁)

最基础的锁,一次只允许一个线程持有。

python 复制代码
import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        with lock:          # 上下文管理器自动获取和释放
            counter += 1

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

for t in threads:
    t.join()

print(counter)   # 应该是 500000(如果没有锁,结果会小于此值)
  • acquire(blocking=True):获取锁。
  • release():释放锁。
  • with lock: 是最推荐的用法,避免忘记释放。

3.2 RLock(可重入锁)

允许同一线程多次获取锁,适用于递归调用或同一线程需要重复加锁的场景。

python 复制代码
rlock = threading.RLock()
rlock.acquire()
rlock.acquire()   # 同一线程可以再次获取
rlock.release()
rlock.release()

3.3 Semaphore(信号量)

控制同时访问资源的线程数量。常用于限制数据库连接数或并发下载数量。

python 复制代码
sem = threading.Semaphore(3)   # 最多允许 3 个线程同时访问

def limited_task():
    with sem:
        print(f"{threading.current_thread().name} 正在运行")
        time.sleep(1)

for i in range(10):
    threading.Thread(target=limited_task).start()

3.4 Event(事件)

用于线程间的信号通知:一个线程设置事件,其他线程等待该事件。

python 复制代码
event = threading.Event()

def waiter():
    print("等待事件...")
    event.wait()
    print("事件已发生,继续执行")

def setter():
    time.sleep(2)
    print("设置事件")
    event.set()

threading.Thread(target=waiter).start()
threading.Thread(target=setter).start()
  • wait([timeout]):阻塞直到事件被设置。
  • set():设置事件(唤醒所有等待线程)。
  • clear():清除事件标志。

3.5 Condition(条件变量)

比 Event 更灵活,允许线程等待某个条件满足后再继续。常用于生产者-消费者模式。

python 复制代码
import threading
import time

condition = threading.Condition()
items = []

def producer():
    for i in range(5):
        with condition:
            items.append(i)
            print(f"生产 {i}")
            condition.notify()   # 通知一个等待的消费者
        time.sleep(0.5)

def consumer():
    while True:
        with condition:
            while not items:
                condition.wait()   # 等待被通知
            item = items.pop(0)
            print(f"消费 {item}")
        time.sleep(1)

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

3.6 Barrier(屏障)

等待所有线程都到达某个点后才一起继续执行。

python 复制代码
barrier = threading.Barrier(3)

def worker():
    print(f"{threading.current_thread().name} 准备就绪")
    barrier.wait()
    print(f"{threading.current_thread().name} 继续执行")

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

4. 📨 线程间通信:Queue 队列

直接共享变量需要繁琐的锁,而 queue.Queue 提供了线程安全的队列,是生产者-消费者模式的首选。

python 复制代码
import threading
import queue
import time

q = queue.Queue(maxsize=10)

def producer():
    for i in range(20):
        q.put(i)
        print(f"生产 {i}")
        time.sleep(0.2)

def consumer():
    while True:
        item = q.get()
        if item is None:   # 毒丸(poison pill)信号
            break
        print(f"消费 {item}")
        q.task_done()      # 标记任务完成
    print("消费者退出")

# 启动生产者
threading.Thread(target=producer).start()

# 启动多个消费者
for _ in range(3):
    threading.Thread(target=consumer, daemon=True).start()

# 等待所有生产任务完成
q.join()
# 发送结束信号
for _ in range(3):
    q.put(None)
  • Queue(maxsize=0):先进先出队列。
  • LifoQueue:后进先出(栈)。
  • PriorityQueue:优先级队列。
  • put(item, block=True, timeout=None):放入元素。
  • get(block=True, timeout=None):取出元素。
  • task_done():通知队列一个任务已完成。
  • join():等待所有任务完成(即 task_done 调用次数等于 put 次数)。

5. 🧰 线程池:高效管理大量线程

手动创建大量线程开销较大,使用线程池可以复用线程,提高性能。Python 标准库提供了 concurrent.futures.ThreadPoolExecutor

python 复制代码
from concurrent.futures import ThreadPoolExecutor
import time

def task(n):
    time.sleep(1)
    return n * n

with ThreadPoolExecutor(max_workers=4) as executor:
    # 方式1:map,保持顺序
    results = executor.map(task, range(10))
    print(list(results))

    # 方式2:submit + futures
    futures = [executor.submit(task, i) for i in range(10)]
    for f in futures:
        print(f.result())

为什么使用线程池?

  • 减少线程创建销毁的开销。
  • 限制并发数量,防止资源耗尽。
  • 简化异常处理和结果收集。

6. 💡 实战案例

案例一:并发下载多个网页

python 复制代码
import threading
import requests

urls = [
    "https://www.python.org",
    "https://www.github.com",
    "https://www.stackoverflow.com",
    # ... 更多 URL
]

results = {}
lock = threading.Lock()

def fetch_url(url):
    try:
        resp = requests.get(url, timeout=5)
        with lock:
            results[url] = resp.status_code
        print(f"完成: {url} - {resp.status_code}")
    except Exception as e:
        with lock:
            results[url] = str(e)

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

for t in threads:
    t.join()

print(results)

案例二:多线程批量处理文件(模拟)

python 复制代码
import threading
import os

def process_file(filepath):
    # 模拟文件处理(例如统计行数、计算哈希等)
    with open(filepath, 'r') as f:
        lines = len(f.readlines())
    print(f"{filepath}: {lines} 行")

def main():
    files = [f for f in os.listdir('.') if f.endswith('.txt')]
    threads = []
    for file in files:
        t = threading.Thread(target=process_file, args=(file,))
        t.start()
        threads.append(t)
    for t in threads:
        t.join()

if __name__ == "__main__":
    main()

案例三:带超时的线程等待

python 复制代码
import threading
import time

def long_task():
    time.sleep(10)
    print("任务完成")

t = threading.Thread(target=long_task)
t.start()
t.join(timeout=3)   # 最多等待 3 秒
if t.is_alive():
    print("任务超时,仍在运行中")
    # 注意:无法强制终止线程,只能设计协作式退出机制

7. ⚠️ 常见陷阱与注意事项

7.1 死锁(Deadlock)

多个线程互相等待对方释放资源,导致程序永久阻塞。

避免方法

  • 使用 with 语句自动释放锁。
  • 按固定顺序获取锁。
  • 使用 threading.RLock 或超时机制。

7.2 线程无法强制终止

Python 线程没有 terminate() 方法。通常通过标志位或队列的毒丸来让线程主动退出。

python 复制代码
stop_flag = threading.Event()

def worker():
    while not stop_flag.is_set():
        # 执行任务
        pass

t = threading.Thread(target=worker)
t.start()
stop_flag.set()   # 通知线程退出
t.join()

7.3 全局解释器锁(GIL)对 CPU 密集型任务的影响

对于纯计算任务,多线程甚至比单线程更慢(因为锁竞争)。请使用 multiprocessing

7.4 避免使用 threading.active_count() 进行精确控制

它返回的是当前活动线程数(包括已启动但未完成的),不保证完全准确。

7.5 线程安全的数据结构

  • queue.Queue 是线程安全的。
  • 列表、字典等内置容器不是线程安全的,需要加锁保护。

7.6 异常处理

线程中的异常不会传播到主线程,务必在线程函数内部捕获并处理。

python 复制代码
def safe_worker():
    try:
        # 可能出错的代码
        pass
    except Exception as e:
        print(f"线程出错: {e}")

8. 📊 threading vs asyncio vs multiprocessing

特性 threading asyncio multiprocessing
并发模型 多线程(抢占式) 协程(协作式) 多进程
适合任务 I/O 密集型 高并发 I/O(网络) CPU 密集型
真正的并行 否(GIL限制)
内存开销 低(共享) 极低
编程复杂度 中等(需处理锁) 较高(async/await) 中等(IPC)
第三方库支持 广泛 需要异步版本 广泛

选型建议

  • 简单并发 I/O,如少量爬虫 → threading
  • 成千上万个网络连接 → asyncio
  • 数值计算、图像处理 → multiprocessing
  • GUI 程序 → threading(保持界面响应)

9. 🎯 总结

通过本文,我们全面学习了 Python threading 模块:

  • 线程基础:创建、启动、守护线程、join
  • 同步机制:Lock、RLock、Semaphore、Event、Condition、Barrier
  • 线程间通信:queue.Queue 安全传递数据
  • 线程池ThreadPoolExecutor 高效管理
  • 实战案例:网页下载、文件处理、超时控制
  • 注意事项:死锁、GIL、无法强制终止等

threading 是 Python 并发编程的基石之一,掌握它将使你能够编写更高效、更响应的应用程序。但请记住,没有银弹------根据任务类型选择合适的并发工具,才能发挥最大的威力。

希望这篇博客能帮助你理清 threading 的脉络。如果在实践中遇到有趣的并发问题,欢迎在评论区讨论!感谢阅读,我们下篇见!🚀

相关推荐
努力学习_小白2 小时前
数据增强——tensorflow
人工智能·python·tensorflow
m0_694845572 小时前
marimo搭建教程:替代Jupyter的交互式开发工具
服务器·ide·python·docker·jupyter·github
csdn2015_2 小时前
Set<String> 类型取第一条记录
开发语言·windows·python
csdn2015_2 小时前
List<String> 转换为Set<String>
windows·python·list
Chen--Xing2 小时前
Python -- 正则表达式
python·正则表达式·数据分析·数据脱敏·2025年能源网络安全大赛
l1t2 小时前
对在aarch64 Linux环境编译安装的CinderX补充测试
linux·运维·服务器·python·jit
getapi2 小时前
Windows 11 安装 uv包括:更新、常用命令、Python 管理、环境切换等,(Astral 的 Python 包/环境工具)完整指南
windows·python·uv
智算菩萨2 小时前
【Pygame】第1章 Pygame入门与环境搭建
python·ai编程·pygame
Dxy12393102162 小时前
Python 使用 `raise` 报错抛出异常显示 Unicode 码如何解决
开发语言·python