Python多进程并行multiprocess基础
- [一、 简介](#一、 简介)
- [二、 核心组件](#二、 核心组件)
- [1、 Process类](#1、 Process类)
- [2、 Pool](#2、 Pool)
- [`Pool.map(func, iterable)`](#
Pool.map(func, iterable)
)- [`Pool.starmap(func, iterable_of_args)`](#
Pool.starmap(func, iterable_of_args)
)- [`Pool.apply(func, args=())`](#
Pool.apply(func, args=())
)- [`Pool.apply_async(func, args=(), callback=None)`](#
Pool.apply_async(func, args=(), callback=None)
)- [`Pool.map_async(func, iterable, callback=None)`](#
Pool.map_async(func, iterable, callback=None)
)- [3、 进程间通信的部分待补充](#3、 进程间通信的部分待补充)
- 三、阻塞和异步的概念
一、 简介
python中多进程编程可以通过内联的模块multiprocess
实现。multiprocess
模块是用于并行处理任务的工具,通过创建多个独立的进程(process)
可以避开Cpython的全局解释器锁(GIL)
,适合于CPU密集型任务。
全局解释器锁(GIL, Global Interpreter Lock)是 CPython(Python 最常用的解释器实现)中的一个互斥锁,它的作用是:在同一时间只允许一个线程执行 Python 字节码(Python 原生代码)
GIL使得在多线程不用担心python对象的内存管理问题,Python 内置的内存分配、引用计数、垃圾回收等操作都只会由一个线程操作,不会"同时"被多个线程修改。内存管理的过程是串行的、安全的,单GIL保护的是python对象的内存操作,并不保证业务逻辑线程安全。
项目 是否线程安全 GIL 是否保护 Python 对象的引用计数 ✅ ✅ Python 内置内存分配器 ✅ ✅ 变量、自增 赋值逻辑 ❌ ❌ 多线程逻辑操作同一 dict/list ❌ ❌
二、 核心组件
1、 Process类
multiprocessing.Process
是multiprocessing 模块中用于创建并管理子进程的类。
基本用法如下
python
from multiprocessing import Process
def worker(num):
print(f"Worker {num} is running")
if __name__ == "__main__":
p = Process(target=worker, arg(1, )) # 创建一个进程,目标函数worker,传入参数1
p.start() # 启动进程,进程开始运行worker函数
p.join() # 主进程等待子进程结束
创建Process对象
Process的构造参数
参数名 | 类型 | 说明 | 默认值 |
---|---|---|---|
group |
None |
保留参数,目前未使用,必须是None |
None |
target |
callable | 进程启动后调用的目标函数 | None |
name |
str | 进程的名字,可以用来区分不同的进程,主要用于调试和日志 | Process-N 自动命名 |
args |
tuple | 传给target 函数的位置参数,元组形式 |
() (空元组) |
kwargs |
dict | 传给target 函数的关键字参数,以字典形式 |
{} (空字典) |
daemon |
bool | 是否将进程设置为守护进程(后台进程),守护进程会随着主进程退出自动结束 | 继承父进程值 |
创建多个进程
python
from multiprocessing import Process
def worker(num):
print(f"Worker {num} started")
# 模拟工作
import time
if num == 2:
time.sleep(4)
else:
time.sleep(2)
print(f"Worker {num} finished")
if __name__ == "__main__":
processes = []
for i in range(4):
p = Process(target=worker, args=(i,))
p.start()
processes.append(p)
for p in processes:
p.join() # 等待所有子进程结束
print("All workers finished")
输出结果:
python
Worker 0 started
Worker 1 started
Worker 2 started
Worker 3 started
Worker 0 finished
Worker 1 finished
Worker 3 finished
Worker 2 finished
All workers finished
使用Process创建的子进程和父进程之间相互独立,子进程有自己的内存空间,父子进程之间不共享内存,需要通过进程间通信协议(IPC)
机制如Queue、Pipe、Manager来传递数据。
涉及进程间通信部分的内容暂时还没有遇到,遇到了再补充吧
2、 Pool
Pool是multiprocess中的进程池对象,用于管理一组进程的工作,自动维护一定数量的进程,避免频繁创建和销毁进程带来的开销。
参数名 | 类型 | 作用 |
---|---|---|
processes |
int |
指定进程数 |
initializer |
callable |
每个子进程启动时运行一次 |
initargs |
tuple |
initializer 的参数 |
maxtasksperchild |
int |
限制每个进程最多执行的任务数 |
context |
特殊对象 | 控制启动方式(spawn 等) |
常用参数是进程数
Pool常用方法如下
Pool.map(func, iterable)
类似于map函数,并行执行func,但是func函数只能接收一个参数,其返回值为一个有序结果列表。
python
from multiprocessing import Pool
def square(x):
return x * x
if __name__ == "__main__":
with Pool(4) as pool:
results = pool.map(square, [1, 2, 3, 4, 5])
print(results) # [1, 4, 9, 16, 25]
Pool.starmap(func, iterable_of_args)
和map基本一样,但是func可以接收多个参数,参数以list[tuple]
的形式传入。
python
from multiprocessing import Pool
def power(base, exp):
return base ** exp
if __name__ == "__main__":
with Pool(3) as pool:
results = pool.starmap(power, [(2, 3), (3, 2), (4, 1)])
print(results) # [8, 9, 4]
Pool.apply(func, args=())
一次调用只会使用一个进程,需要多次调用,不是真正的并行,同时会阻塞
python
from multiprocessing import Pool
def add(x, y):
return x + y
if __name__ == "__main__":
with Pool(2) as pool:
result = pool.apply(add, (3, 5))
print(result) # 8
Pool.apply_async(func, args=(), callback=None)
和apply不同的是apply_async是异步非阻塞的,**"异步非阻塞"**是指:任务会被并行执行,主线程不需要等待任务完成就可以继续往下运行。可以在稍后用 .get() 来获取结果,也可以设置回调函数 callback= 自动处理结果。
python
from multiprocessing import Pool
import time
def slow_function(x):
time.sleep(2)
return x * 2
if __name__ == '__main__':
with Pool(2) as pool:
# 异步提交任务
result1 = pool.apply_async(slow_function, args=(10,))
result2 = pool.apply_async(slow_function, args=(20,))
# 主进程继续执行,不会等待
print("Tasks submitted... doing other things meanwhile...")
# 等待并获取结果
print("Result 1:", result1.get())
print("Result 2:", result2.get())
程序会在 ~2 秒后输出两个结果,而不是 ~4 秒。
Pool.map_async(func, iterable, callback=None)
是map的异步非阻塞版。
python
from multiprocessing import Pool
def square(x):
return x * x
with Pool(4) as pool:
result = pool.map_async(square, [1, 2, 3, 4])
print("Tasks submitted.")
# 等待最多5秒后拿结果
print("Results:", result.get(timeout=5)) # [1, 4, 9, 16]
3、 进程间通信的部分待补充
未完待续...
三、阻塞和异步的概念
阻塞和异步是两个经常一起出现的词
首先明确一些阻塞和异步的概念
异步
:是指任务调度的方式,调用任务者发起一个任务后,不等待任务完成,接着执行后面的代码,任务在将来某个时间点完成,结果通过回调函数、事件通知、等待对象获得。
非阻塞
:是发起一项操作,不用等待操作完成就能继续做别的事,常见于IO,更侧重于操作
。
几种方法的对比
方法 | 是否阻塞 | 是否可多参数 | 返回值类型 | 回调支持 |
---|---|---|---|---|
map() |
✅ 是 | ❌ 否 | 结果列表 | ❌ 无 |
starmap() |
✅ 是 | ✅ 是 | 结果列表 | ❌ 无 |
apply() |
✅ 是 | ✅ 是 | 单个结果 | ❌ 无 |
apply_async() |
❌ 否 | ✅ 是 | AsyncResult |
✅ 支持 |
map_async() |
❌ 否 | ❌ 否 | AsyncResult |
✅ 支持 |