Python 多线程基础与实战详解
1. 什么是多线程?
在计算机操作系统中,多任务处理是指系统同时执行多个任务的能力。进程(Process)是资源分配的最小单位,而线程(Thread)是 CPU 调度的最小单位。一个进程可以包含多个线程,这些线程共享进程的资源(如内存空间、文件句柄等),但拥有独立的栈空间和寄存器状态。
本文详细讲解了 Python 多线程的概念、创建方式及线程安全机制。内容涵盖进程与线程的区别、三种线程创建方法、GIL 锁的影响、竞态问题及互斥锁解决方案,并对比了 threading 与 multiprocessing 的适用场景,旨在帮助开发者掌握 Python 并发编程的核心技能。

在计算机操作系统中,多任务处理是指系统同时执行多个任务的能力。进程(Process)是资源分配的最小单位,而线程(Thread)是 CPU 调度的最小单位。一个进程可以包含多个线程,这些线程共享进程的资源(如内存空间、文件句柄等),但拥有独立的栈空间和寄存器状态。
在 Python 中,多线程允许程序在同一时间片内交替执行多个任务,从而提高程序的响应速度和吞吐量,特别是在涉及大量 I/O 操作(如网络请求、文件读写)的场景下。通过并发执行,程序可以在等待某个耗时操作完成的同时处理其他逻辑,避免阻塞主流程。
并非所有场景都适合多线程。由于 Python 的全局解释器锁(GIL)机制,多线程在 CPU 密集型任务中并不能真正利用多核 CPU 的优势,甚至可能因为上下文切换开销导致性能下降。
适用场景:
不适用场景:
multiprocessing 模块来绕过 GIL。Python 标准库提供了 threading 模块来处理多线程。无需额外安装,直接导入即可。
这是最面向对象的方式。通过继承 threading.Thread 并重写 run 方法来定义线程逻辑。这种方式适合需要维护线程状态或复用的场景。
import threading
import time
class MyThread(threading.Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f"{self.name} 开始运行")
time.sleep(2)
print(f"{self.name} 运行结束")
# 创建并启动线程
t1 = MyThread("线程 A")
t2 = MyThread("线程 B")
t1.start()
t2.start()
t1.join()
t2.join()
print("所有线程执行完毕")
这种方式更灵活,适用于简单的脚本。通过 target 参数指定要执行的函数,args 和 kwargs 传递参数。
from threading import Thread
import time
def worker(task_id):
print(f"执行任务 {task_id}")
time.sleep(1)
threads = []
for i in range(3):
t = Thread(target=worker, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
守护线程会在主线程结束时自动退出,常用于后台服务。默认情况下线程是非守护线程,主线程会等待它们结束。
import threading
import time
def background_task():
while True:
print("后台运行中...")
time.sleep(1)
t = threading.Thread(target=background_task, daemon=True)
t.start()
time.sleep(3)
print("主线程结束,守护线程将随之终止")
start(): 启动线程,调用 run() 方法。注意不要直接调用 run(),否则它将在当前线程同步执行。join(): 阻塞当前线程,直到调用 join() 的线程执行完毕。这对于确保主线程等待子线程完成至关重要,常用于数据收集或顺序依赖场景。is_alive(): 检查线程是否仍在运行,返回布尔值。每个线程都有唯一的名称,可以通过 threading.current_thread().name 获取。默认名称为 Thread-N,建议设置有意义的名称以便调试。
对于需要管理大量线程的场景,推荐使用 concurrent.futures.ThreadPoolExecutor。它自动管理线程生命周期,简化了代码结构。
from concurrent.futures import ThreadPoolExecutor
import time
def task(n):
time.sleep(1)
return n * n
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(task, range(10)))
print(results)
CPython 解释器中存在一个全局锁(GIL),同一时刻只允许一个线程执行 Python 字节码。这意味着在单核 CPU 上,多线程无法并行执行 Python 代码;在多核 CPU 上,也受限于 GIL 的切换开销。GIL 的存在是为了保证 C 扩展库的安全性,但也限制了 Python 多线程的性能。
当多个线程同时修改共享数据时,可能导致数据不一致。例如,两个线程同时对一个变量进行自增操作。如果不加保护,最终结果往往小于预期。
counter = 0
def unsafe_increment():
global counter
for _ in range(100000):
temp = counter
temp += 1
counter = temp
# 如果不加锁,最终结果往往小于 200000
使用 threading.Lock 保护临界区,确保同一时刻只有一个线程能访问共享资源。with 语句可以自动获取和释放锁,即使发生异常也能正确释放。
import threading
lock = threading.Lock()
counter = 0
def safe_increment():
global counter
for _ in range(100000):
with lock: # 自动获取和释放锁
counter += 1
t1 = threading.Thread(target=safe_increment)
t2 = threading.Thread(target=safe_increment)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"最终计数值:{counter}") # 输出应为 200000
如果一个线程需要多次获取同一个锁(例如在递归调用中),普通 Lock 会导致死锁。此时应使用 RLock,它允许同一个线程重复获取锁。
queue.Queue 代替手动锁管理数据传递,它是线程安全的。multiprocessing,I/O 密集型用 threading,异步高并发考虑 asyncio。sys._current_frames() 打印所有线程的堆栈信息来排查死锁问题。Python 多线程是构建高性能并发应用的重要工具。理解其底层机制(如 GIL)、掌握正确的创建方式以及学会处理线程安全问题,是每一位 Python 开发者必须掌握的核心技能。在实际开发中,应根据具体业务场景权衡利弊,选择合适的并发模型,确保系统的稳定性与效率。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online