一、多线程基础核心(重点)
1. 线程与进程的本质区别
- 核心定义 :
进程是操作系统"分配资源"的最小单位(如内存、文件描述符),线程是"CPU调度"的最小单位;一个进程包含多个线程,线程共享进程的资源,无进程级隔离(进程崩溃则其下所有线程终止)。
- 关键差异对比(重点):
多线程与多进程对比
| 对比维度 |
多线程(Threading) |
多进程(Multiprocessing) |
| 资源开销 |
轻量,创建/销毁快 |
重量,创建/销毁慢 |
| GIL影响 |
受CPython的GIL限制,CPU密集型无法并行 |
每个进程有独立解释器,绕开GIL,支持并行 |
| 内存共享 |
共享进程内存,易有线程安全问题 |
内存隔离,需通过IPC(管道、共享内存)通信 |
| 适用场景 |
IO密集型(如网络请求、文件读写) |
CPU密集型(如数据计算、图像处理) |
- 举例 :
- 多线程场景:用3个线程同时下载3部电影(下载时需等待网络响应,属于IO密集,线程切换快,效率高);
- 多进程场景:用4个进程计算1-100000的质数(计算需占用CPU,属于CPU密集,多进程可利用4核CPU同时计算,速度比单进程快近4倍)。
2. GIL(全局解释器锁)的特性(重点)
- 定义:CPython解释器的锁机制,确保同一时刻只有1个线程执行Python字节码。
- 优缺点 :
- 优点:简化内存管理(避免多线程同时修改对象)、单线程运行稳定;
- 缺点:多核CPU下,CPU密集型多线程无法"真正并行",线程数量过多时会因频繁切换损耗性能。
- 举例 :
用8线程处理大型Excel数据计算(CPU密集),实际只有1个线程在执行,其余线程排队等待GIL,最终效率甚至比单线程低(切换线程需要时间);若换成8进程,每个进程占用1个CPU核心,8个核心同时计算,效率会大幅提升。
二、Python多线程实现(threading模块)
1. 线程创建的3种方式(重点)
(1)基础无参线程
- 核心逻辑 :通过
threading.Thread绑定目标函数,用start()启动线程(不可直接调用函数,否则会在主线程同步执行)。
- 例如:创建1个线程打印"任务执行",主线程同时打印"主线程工作",实现并发:
import time
from threading import Thread
def task():
print("线程:任务执行中")
time.sleep(2) # 模拟任务耗时
print("线程:任务完成")
if __name__ == '__main__':
t = Thread(target=task) # 绑定任务函数
t.start() # 启动线程
print("主线程:正在处理其他工作")
t.join() # 等待线程结束,避免主线程先退出
- 运行结果:先打印"主线程:正在处理其他工作"和"线程:任务执行中",2秒后打印"线程:任务完成"(体现并发)。
(2)带参线程(args/kwargs)
- 核心逻辑 :用
args传"位置参数"(元组形式,单个参数需加逗号),用kwargs传"关键字参数"(字典形式)。
- (args传参):线程向指定用户打招呼:
from threading import Thread
def say_hi(name):
print(f"你好,{name}!")
t = Thread(target=say_hi, args=("小明",)) # args是元组,"小明"后加逗号
t.start() # 输出:你好,小明!
t = Thread(target=lambda name, age: print(f"{name}今年{age}岁"),
kwargs={"name": "小红", "age": 20})
t.start() # 输出:小红今年20岁
(3)Timer定时器线程
- 核心逻辑 :
threading.Timer是Thread的子类,指定"延迟时间"后执行任务,可通过cancel()取消未执行的任务。
- 例如:5秒后提醒喝水,若3秒内取消则不提醒:
import threading
import time
def remind():
print("该喝水了!")
# 创建定时器:延迟5秒执行remind
timer = threading.Timer(5, remind)
timer.start()
time.sleep(3) # 主线程等待3秒
timer.cancel() # 3秒后取消定时器,remind不会执行
三、线程间通信(queue模块,重点)
核心队列类型:解决线程安全的数据传递
1. Queue(先进先出,FIFO)
- 核心逻辑 :多线程安全的队列,
put()放入数据(队列满时阻塞),get()取出数据(队列空时阻塞),配合task_done()和join()确保任务完成。
- 例如(生产者-消费者模式):1个生产者做包子,2个消费者吃包子:
from threading import Thread
from queue import Queue
import time
def producer(q): # 生产者:10秒内做5个包子
for i in range(5):
q.put(f"包子{i}")
print(f"生产者:做好了{q.qsize()}个包子")
time.sleep(2) # 每2秒做1个
def consumer(q, name): # 消费者:吃包子
while True:
bun = q.get()
print(f"{name}:吃掉了{bun}")
q.task_done() # 标记"这个包子已吃完"
time.sleep(1)
if __name__ == '__main__':
q = Queue(3) # 队列最多存3个包子(避免堆太多)
# 启动生产者和2个消费者
Thread(target=producer, args=(q,)).start()
Thread(target=consumer, args=(q, "消费者A")).start()
Thread(target=consumer, args=(q, "消费者B")).start()
q.join() # 等待所有包子被吃完
print("所有包子都吃完了!")
2. LifoQueue(后进先出,栈结构)
from queue import LifoQueue
q = LifoQueue()
q.put("盘子1") # 先放盘子1
q.put("盘子2") # 再放盘子2
print(q.get()) # 先拿盘子2(输出:盘子2)
print(q.get()) # 再拿盘子1(输出:盘子1)
3. PriorityQueue(优先级队列)
- 核心逻辑 :元素以
(优先级, 数据)形式放入,优先级数字越小,越先取出。
- 例如:医院急诊(优先级1=病危,优先级2=病重,优先级3=普通):
from queue import PriorityQueue
q = PriorityQueue()
q.put((3, "普通患者")) # 优先级3
q.put((1, "病危患者")) # 优先级1(最高)
q.put((2, "病重患者")) # 优先级2
print(q.get()[1]) # 先处理病危患者(输出:病危患者)
print(q.get()[1]) # 再处理病重患者(输出:病重患者)
print(q.get()[1]) # 最后处理普通患者(输出:普通患者)
四、线程同步机制(解决线程安全问题,重点)
1. 锁(Lock/RLock)
(1)Lock(互斥锁)
- 核心逻辑 :同一时刻仅1个线程能获取锁,未获取则阻塞;需成对使用
acquire()(获取)和release()(释放),或用with自动管理(避免死锁)。
- 例如(解决全局变量竞争):2个线程同时给计数器加1000次,无锁会出错,有锁则正确:
from threading import Thread, Lock
import time
count = 0
lock = Lock() # 创建互斥锁
def add():
global count
for _ in range(1000):
with lock: # 自动获取/释放锁,保护临界区
count += 1 # 临界区:多线程共享操作
# 启动2个线程
t1 = Thread(target=add)
t2 = Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()
print(count) # 输出:2000(无锁时可能小于2000)
(2)RLock(可重入锁)
- 例如(嵌套锁场景):线程内嵌套调用加锁函数,RLock允许同一线程多次获取锁:
from threading import RLock
rlock = RLock()
def func1():
with rlock:
print("执行func1")
func2() # 嵌套调用func2
def func2():
with rlock: # 同一线程可再次获取锁
print("执行func2")
func1() # 正常输出:执行func1 → 执行func2(若用Lock会死锁)
2. 信号量(Semaphore)
- 核心逻辑 :控制"同时访问资源的线程数量",维护一个计数器,
acquire()减1,release()加1,计数器为0时阻塞。
- 例如(限制2个线程同时用打印机):
from threading import Thread, Semaphore
import time
sem = Semaphore(2) # 打印机最多允许2个线程同时使用
def use_printer(name):
with sem: # 自动获取/释放信号量
print(f"{name}开始打印")
time.sleep(3) # 模拟打印耗时
print(f"{name}打印完成")
# 5个线程竞争打印机
for i in range(5):
Thread(target=use_printer, args=(f"用户{i}",)).start()
# 结果:始终只有2个用户在打印,其余等待
3. 事件(Event)
- 核心逻辑 :通过内部"True/False"标志通信,
set()设为True(唤醒所有等待线程),clear()设为False,wait()阻塞至标志为True。
- 例如(老师发令后学生才跑步):
from threading import Thread, Event
import time
event = Event() # 初始标志为False
def student(name):
print(f"{name}准备就绪,等待老师发令")
event.wait() # 阻塞至标志为True
print(f"{name}开始跑步")
# 5个学生准备
for i in range(5):
Thread(target=student, args=(f"学生{i}",)).start()
time.sleep(2) # 老师准备2秒
event.set() # 发令(标志设为True)
# 结果:2秒后所有学生同时开始跑步
五、线程池(concurrent.futures.ThreadPoolExecutor,重点)
1. 核心优势
- 降低开销:重用线程,避免频繁创建/销毁线程;
- 提高效率:无需等待线程创建,任务来了直接用空闲线程;
- 便于管理:控制最大线程数,避免占用过多资源。
2. 2种核心使用方式
(1)submit():提交单个任务,返回Future对象(用result()获取结果)
from concurrent.futures import ThreadPoolExecutor
def square(num):
return num * num
# 创建最多2个线程的线程池
with ThreadPoolExecutor(max_workers=2) as executor:
# 提交5个任务
futures = [executor.submit(square, i) for i in [1,2,3,4,5]]
# 获取结果
for future in futures:
print(f"平方结果:{future.result()}")
# 输出:1、4、9、16、25
(2)map():批量提交任务,直接返回结果迭代器(按输入顺序输出)
from concurrent.futures import ThreadPoolExecutor
import time
def download(file):
time.sleep(1) # 模拟下载耗时
return f"{file}下载完成"
with ThreadPoolExecutor(max_workers=2) as executor:
files = ["视频1.mp4", "图片2.jpg", "文档3.pdf"]
results = executor.map(download, files) # 批量执行
for res in results:
print(res)
# 输出:视频1.mp4下载完成 → 图片2.jpg下载完成 → 文档3.pdf下载完成