学习目标
通过本文学习,你将掌握:
- 理解并发与并行的区别以及实际应用场景
- 掌握 Python 多进程编程方式
- 掌握 Process 创建、管理以及进程池的使用
- 理解进程间通信机制 Queue
- 掌握 Python 多线程创建方式
- 掌握线程池 ThreadPoolExecutor 的使用
- 理解线程安全问题以及互斥锁解决方案
一、并发与并行
在现代计算机中,一个程序往往需要同时处理多个任务,例如:
- 下载文件的同时更新进度条
- 浏览网页时同时加载图片和文字
- 游戏中同时处理人物移动、声音播放和网络通信
这些场景都涉及多任务处理。
多任务处理主要有两个概念:
- 并发(Concurrency)
- 并行(Parallelism)
虽然二者看起来类似,但是本质区别在于:
是否真正同时执行。
1. 并发
1.1 什么是并发
并发指的是:
一个 CPU 在同一时间段内处理多个任务,通过快速切换任务,让用户感觉多个任务同时进行。
例如:
一个人:
- 回复消息
- 喝水
实际过程:
回复一句消息
↓
喝一口水
↓
继续回复
↓
继续喝水
微观上:
串行执行
宏观上:
同时完成多个任务
1.2 并发特点
① 单 CPU 也可以实现
一个 CPU 核心通过任务切换实现多个任务处理。
② 存在任务切换
CPU 会在不同任务之间快速切换。
③ 适合 IO 密集型任务
例如:
- 文件读写
- 网络请求
- 数据库访问
原因:
当一个任务等待 IO 时:
CPU空闲
↓
切换执行其他任务
提高 CPU 利用率。
2. 并行
2.1 什么是并行
并行指:
多个 CPU 核心真正同时执行多个任务。
例如:
两个人:
A:回复消息
B:喝水
两个任务同时发生。
2.2 并行特点
① 需要多个 CPU 核心
例如:
4核 CPU:
CPU1 → 任务A
CPU2 → 任务B
CPU3 → 任务C
CPU4 → 任务D
② 没有任务切换
多个任务真正同时运行。
③ 适合 CPU 密集型任务
例如:
- 大规模计算
- 图像处理
- 数据分析
- AI训练
3. 并发与并行区别
| 对比 | 并发 | 并行 |
|---|---|---|
| CPU数量 | 一个或多个 | 多个 |
| 执行方式 | 交替执行 | 同时执行 |
| 本质 | 宏观同时,微观串行 | 真正同时 |
| 目标 | 提高资源利用率 | 提升计算速度 |
| 场景 | IO密集型 | CPU密集型 |
二、多进程编程
1. 什么是进程
进程是:
操作系统进行资源分配的基本单位。
例如:
电脑运行:
- 浏览器
- 微信
- Python程序
它们都是不同进程。
进程特点
① 每个进程拥有独立内存
例如:
进程A
内存空间A
进程B
内存空间B
二者互不影响。
② 进程之间不能直接共享数据
需要通过:
- Queue
- Pipe
- Manager
进行通信。
③ 稳定性高
一个进程崩溃:
不会影响其他进程。
2. 创建进程
Python通过:
python
multiprocessing
模块实现多进程。
方法一:Process创建进程
基本格式:
python
Process(
target=函数,
args=参数
)
示例:
python
import multiprocessing
def task(name):
print(name)
if __name__=="__main__":
p=multiprocessing.Process(
target=task,
args=("hello",)
)
p.start()
其中:
- target:指定执行任务
- args:传递参数
- start():启动进程
start() 与 run()区别
start()
作用:
真正创建新的进程。
执行:
主进程
|
|
子进程
run()
只是普通方法调用:
python
p.run()
不会创建新进程。
方法二:继承 Process 类
适合复杂任务。
示例:
python
import multiprocessing
class MyProcess(
multiprocessing.Process
):
def run(self):
print("子进程运行")
if __name__=="__main__":
p=MyProcess()
p.start()
3. Process常用方法
| 方法 | 作用 |
|---|---|
| start() | 启动进程 |
| join() | 等待进程结束 |
| terminate() | 强制结束进程 |
| kill() | 杀死进程 |
| is_alive() | 判断是否运行 |
三、进程池 Pool
1. 为什么使用进程池
如果创建:
100个任务
手动创建:
100个Process
效率低。
进程池:
提前创建:
几个进程
不断重复执行任务。
创建进程池
python
pool=multiprocessing.Pool(
processes=4
)
表示:
创建4个工作进程。
2. Pool常用方法
apply()
同步执行:
特点:
提交任务
↓
等待完成
↓
继续执行
apply_async()
异步执行:
特点:
提交任务
↓
继续执行其他代码
↓
之后获取结果
适合大量任务。
四、进程间通信 Queue
为什么需要通信?
因为:
进程A内存
≠
进程B内存
不能直接访问。
multiprocessing.Queue
示例:
python
queue.put(data)
data=queue.get()
作用:
实现:
进程A
↓
Queue
↓
进程B
Pool中使用Queue
普通:
python
multiprocessing.Queue()
不能用于Pool。
需要:
python
multiprocessing.Manager().Queue()
五、多线程编程
1. 什么是线程
线程是:
CPU调度和执行的基本单位。
特点:
- 依赖进程存在
- 一个进程可以拥有多个线程
- 多线程共享进程资源
例如:
浏览器:
浏览器进程
├─ 页面线程
├─ 下载线程
└─ 网络线程
2. 创建线程
Python:
python
threading.Thread
方法一:Thread创建
python
import threading
def task():
print("线程运行")
t=threading.Thread(
target=task
)
t.start()
方法二:继承Thread
python
class Worker(threading.Thread):
def run(self):
print("线程任务")
t=Worker()
t.start()
六、线程池 ThreadPoolExecutor
当线程数量很多:
例如:
1000个线程
管理困难。
线程池:
提前创建线程。
创建:
python
from concurrent.futures import ThreadPoolExecutor
pool=ThreadPoolExecutor(
max_workers=5
)
提交任务:
python
future=pool.submit(
task
)
获取结果:
python
future.result()
七、线程安全问题
1. 什么是线程安全
多个线程共享数据时:
可能出现:
线程A读取数据
线程B修改数据
线程A再次读取
数据错误
这就是线程安全问题。
2. 示例
多个线程:
python
g_num +=1
实际上包含:
读取
计算
写入
多个线程同时执行:
可能出现覆盖。
八、互斥锁 Lock
解决线程安全:
使用:
python
threading.Lock()
作用:
同一时间只允许一个线程修改数据。
使用方式
获取锁:
python
lock.acquire()
释放锁:
python
lock.release()
示例:
python
import threading
g_num=0
lock=threading.Lock()
def add():
global g_num
for i in range(10):
lock.acquire()
g_num+=1
lock.release()
执行过程:
线程A获得锁
↓
修改数据
↓
释放锁
↓
线程B获得锁
保证数据正确。
九、进程与线程总结
| 对比 | 进程 | 线程 |
|---|---|---|
| 资源 | 独立资源 | 共享资源 |
| 开销 | 大 | 小 |
| 通信 | 复杂 | 简单 |
| 稳定性 | 高 | 较低 |
| 适合 | CPU密集型 | IO密集型 |
| Python模块 | multiprocessing | threading |
十、总结
Python多任务编程主要包含:
多进程
特点:
- 独立内存
- 利用多核心CPU
- 适合计算任务
核心:
Process
Pool
Queue
多线程
特点:
- 共享内存
- 创建速度快
- 适合等待任务
核心:
Thread
ThreadPoolExecutor
Lock
最终选择:
CPU密集型
↓
多进程
IO密集型
↓
多线程
掌握并发、进程、线程以及线程安全,是 Python 后端开发、爬虫、自动化、大数据处理和人工智能开发的重要基础。