前言
在编程中,我们经常遇到需要同时处理多个任务的场景:比如一边下载文件、一边显示进度,一边爬取网页、一边保存数据,一边处理数据、一边响应用户操作。如果用单线程依次执行,效率会极低,而多线程就是解决这类问题的核心技术。
Python 作为一门简洁高效的编程语言,内置了完善的多线程支持,无需复杂配置即可快速实现并发编程。本教程将从基础概念、核心模块、实战案例、避坑指南四个维度,带你从零掌握 Python 多线程,轻松应对日常开发中的并发需求。
一、多线程核心概念:先搞懂这 3 个基础问题
- 什么是线程?
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。简单来说:
进程:一个运行的程序(比如打开的 Python 解释器、浏览器),拥有独立的内存空间;
线程:进程中的执行分支,一个进程至少有一个主线程,可创建多个子线程,共享进程的内存空间。
打个比方:进程是一家工厂,线程是工厂里的工人,工厂提供场地和设备(内存),多个工人可以同时干活(并发执行),效率更高。 - 什么是多线程?
多线程就是在一个进程中同时运行多个线程,让程序能够并发处理多个任务。比如:
视频软件:一边播放画面(线程 1),一边播放声音(线程 2),一边加载后续视频(线程 3);
聊天软件:一边接收消息(线程 1),一边发送消息(线程 2),一边刷新好友列表(线程 3)。 - Python 多线程的特点:GIL 锁
很多新手会问:Python 多线程真的能同时执行吗?这里需要了解GIL(全局解释器锁):
GIL 是 CPython(Python 官方解释器)的机制,它确保同一时间只有一个线程执行 Python 字节码;
因此,Python 多线程不适合 CPU 密集型任务(如大量计算),更适合I/O 密集型任务(如文件读写、网络请求、用户交互);
I/O 密集型任务中,线程会频繁等待(如下载文件时等待网络响应),多线程可以充分利用等待时间,大幅提升效率。
二、Python 多线程核心模块:threading
Python3 中推荐使用threading 模块实现多线程,它替代了旧的 thread 模块,功能更完善、使用更安全。threading 模块提供了创建线程、管理线程、线程同步等核心功能,是 Python 多线程开发的必备工具。 - 线程的创建方式一:直接创建(最简单)
通过threading.Thread类直接创建线程,传入需要执行的函数和参数,步骤如下:
导入 threading 模块;
定义需要并发执行的函数;
创建 Thread 对象,指定 target(目标函数)和 args(函数参数);
调用 start () 方法启动线程。
代码示例:
python
运行
import threading
import time
定义需要执行的任务函数
def task(name, delay):
print(f"线程【{name}】启动,等待{delay}秒")
time.sleep(delay) # 模拟I/O等待(如网络请求、文件读写)
print(f"线程【{name}】执行完成")
if name == "main":
print("主线程启动")
# 创建线程1:执行task函数,参数为("线程1", 2)
t1 = threading.Thread(target=task, args=("线程1", 2))
# 创建线程2:执行task函数,参数为("线程2", 1)
t2 = threading.Thread(target=task, args=("线程2", 1))
# 启动线程
t1.start()
t2.start()
print("主线程结束")
运行结果:
plaintext
主线程启动
线程【线程1】启动,等待2秒
线程【线程2】启动,等待1秒
主线程结束
线程【线程2】执行完成
线程【线程1】执行完成
注意:start()是启动线程的关键,不能直接调用task()(否则是单线程执行)。
- 线程的创建方式二:继承 Thread 类(更灵活)
当任务逻辑复杂时,可以通过继承 threading.Thread 类,重写run()方法实现线程,这种方式扩展性更强,适合封装复杂任务。
代码示例:
python
运行
import threading
import time
自定义线程类,继承threading.Thread
class MyThread(threading.Thread):
# 初始化方法,接收参数
def __init__(self, name, delay):
super().__init__() # 调用父类构造方法
self.name = name # 线程名称
self.delay = delay # 等待时间
# 重写run方法:线程执行的核心逻辑
def run(self):
print(f"线程【{self.name}】启动,等待{self.delay}秒")
time.sleep(self.delay)
print(f"线程【{self.name}】执行完成")
if name == "main":
print("主线程启动")
# 创建自定义线程对象
t1 = MyThread("线程1", 2)
t2 = MyThread("线程2", 1)
# 启动线程(自动调用run方法)
t1.start()
t2.start()
print("主线程结束")
运行结果与方式一一致,这种方式适合需要封装属性和方法的复杂场景。
- 线程常用方法:必须掌握
threading 模块提供了多个管理线程的方法,日常开发高频使用:
threading.Thread(target=函数, args=(参数,)):创建线程对象;
start():启动线程(进入就绪状态,等待 CPU 调度);
join([timeout]):主线程等待子线程执行完成,再继续执行;timeout 为可选等待时间(秒);
is_alive():判断线程是否正在执行,返回 True/False;
threading.current_thread():获取当前执行的线程对象;
threading.active_count():获取当前活跃的线程数量。
join () 方法实战:让主线程等待子线程完成再结束
python
运行
import threading
import time
def task(name, delay):
print(f"线程【{name}】启动")
time.sleep(delay)
print(f"线程【{name}】完成")
if name == "main":
t1 = threading.Thread(target=task, args=("线程1", 2))
t2 = threading.Thread(target=task, args=("线程2", 1))
t1.start()
t2.start()
# 主线程等待t1、t2执行完成
t1.join()
t2.join()
# 这行代码会在子线程全部完成后执行
print("所有线程执行完毕,主线程结束")
运行结果:
plaintext
线程【线程1】启动
线程【线程2】启动
线程【线程2】完成
线程【线程1】完成
所有线程执行完毕,主线程结束
三、线程同步:解决多线程资源竞争问题
多线程共享进程的内存空间,当多个线程同时修改同一个共享资源时,会出现数据混乱(资源竞争),这时候需要线程同步机制保证数据安全。
- 资源竞争问题演示
python
运行
import threading
共享资源:全局变量
count = 0
def addcount():
global count
for in range(100000):
count += 1 # 多个线程同时修改count
if name == "main":
t1 = threading.Thread(target=add_count)
t2 = threading.Thread(target=add_count)
t1.start()
t2.start()
t1.join()
t2.join()
# 预期结果:200000,实际结果小于200000
print(f"最终count值:{count}")
问题原因:count += 1不是原子操作(分为读取、修改、写入三步),多个线程交替执行时,会导致数据覆盖。
- 线程锁:Lock(互斥锁)解决竞争
threading 模块提供Lock 锁,确保同一时间只有一个线程执行修改共享资源的代码,步骤:
创建锁对象:lock = threading.Lock();
加锁:lock.acquire();
释放锁:lock.release()(必须释放,否则会造成死锁);
推荐使用with语句(自动加锁、释放锁,避免死锁)。
修复代码:
python
运行
import threading
count = 0
lock = threading.Lock() # 创建互斥锁
def addcount():
global count
for in range(100000):
with lock: # 自动加锁,代码块执行完自动释放锁
count += 1
if name == "main":
t1 = threading.Thread(target=add_count)
t2 = threading.Thread(target=add_count)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"最终count值:{count}") # 结果:200000,数据安全
- 死锁问题:必须避免
死锁是指多个线程互相等待对方释放锁,导致程序卡住。比如:线程 1 持有锁 A,等待锁 B;线程 2 持有锁 B,等待锁 A,双方无限等待。
避免死锁的原则:
避免一个线程同时获取多个锁;
避免一个线程在锁内占用多个资源;
定时释放锁,不无限等待。
四、多线程实战案例:I/O 密集型任务效率对比
多线程最大的优势是提升 I/O 密集型任务的效率,我们通过模拟网络请求对比单线程和多线程的执行时间。 - 单线程执行
python
运行
import time
模拟网络请求(I/O等待)
def request(url):
print(f"开始请求:{url}")
time.sleep(2) # 模拟2秒网络等待
print(f"请求完成:{url}")
if name == "main":
start = time.time()
# 依次请求3个网址
request("https://www.baidu.com")
request("https://www.google.com")
request("https://www.github.com")
end = time.time()
print(f"单线程总耗时:{end - start:.2f}秒")
运行结果:单线程总耗时≈6 秒(依次等待,2×3=6)
- 多线程执行
python
运行
import threading
import time
def request(url):
print(f"开始请求:{url}")
time.sleep(2)
print(f"请求完成:{url}")
if name == "main":
start = time.time()
# 创建3个线程
t1 = threading.Thread(target=request, args=("https://www.baidu.com",))
t2 = threading.Thread(target=request, args=("https://www.google.com",))
t3 = threading.Thread(target=request, args=("https://www.github.com",))
# 启动线程
t1.start()
t2.start()
t3.start()
# 等待所有线程完成
t1.join()
t2.join()
t3.join()
end = time.time()
print(f"多线程总耗时:{end - start:.2f}秒")
运行结果:多线程总耗时≈2 秒(并发执行,只等待一次 2 秒)
结论:I/O 密集型任务中,多线程效率是单线程的 N 倍(N 为线程数量),优势极其明显!
五、Python 多线程避坑指南(新手必看)
不要用多线程处理 CPU 密集型任务:GIL 锁限制,多线程无法并行计算,效率不如单线程,推荐用multiprocessing多进程;
线程不是越多越好:线程过多会导致 CPU 频繁切换线程(线程调度开销),反而降低效率,I/O 密集型任务一般设置 5-20 个线程即可;
共享资源必须加锁:多个线程修改同一变量、文件、数据库时,一定要用 Lock 锁保证数据安全;
守护线程:通过threading.Thread(daemon=True)设置守护线程,主线程退出时,守护线程会自动结束(适合后台任务);
避免死锁:严格控制锁的获取和释放,不用嵌套多个锁,不无限等待锁。
六、总结
Python 多线程是处理I/O 密集型任务的利器,核心知识点总结:
核心模块:threading,通过Thread类创建线程,start()启动,join()等待;
两种创建方式:直接传函数、继承 Thread 类;
线程同步:Lock 锁解决资源竞争,保证数据安全;
适用场景:网络请求、文件读写、用户交互、数据爬取等 I/O 密集型任务;
核心优势:充分利用 I/O 等待时间,大幅提升程序执行效率。
掌握本教程的内容,你已经可以独立完成 Python 多线程开发,应对日常工作中的并发需求。后续可以深入学习线程池(concurrent.futures.ThreadPoolExecutor)、信号量、事件等高级线程同步机制,让多线程编程更高效、更优雅!
参考:http://phdhk.cn