背景
软件开发过程中,有时会需要程序同时处理多个任务,又或者需要程序处理的某一个任务各个部分不相互排队;
这时,我们会使用多线程、多进程等方式,让程序进行并行计算;
python 提供了 multiprocessing 库用于支持多进程编程;同时这也是 Python 官方推荐的多线程替代方案;
简介
multiprocessing 是 Python 的一个标准库,它提供了创建和管理进程的功能,使得开发者能够利用多核处理器进行并行计算。
使用 multiprocessing 可以使得 Python 程序在多核或多处理器机器上更加高效地运行。
进程创建:使用 Process 类来创建进程。
进程池Pool:提供了一个简单的接口来处理多个进程的并行执行,特别是用于迭代任务,如 map 和 apply_async。
进程间通信:支持进程间的通信,如管道(Pipe)、队列(Queue)和共享内存。
共享状态:虽然进程通常不共享状态,但 multiprocessing 提供了一些方式来共享状态,如 Value 和 Array。
同步:提供锁(Lock)和信号量(Semaphore)等同步机制,用于控制对共享资源的访问。
进程创建
multiprocessing.Process 类用于创建新的进程对象;
可以通过将函数作为参数传递给 Process 对象来创建新的进程;
import multiprocessing as mp
def func_execute():
print(" execute ")
if __name__ == "__main__":
process = mp.Process(target=func_execute)
process.start()
process.join()
start()
方法用于启动子进程; join()
方法用于等待进程执行完毕;
通过在主程序中调用 join()
,可以让主程序等待子进程的结束,确保主程序在子进程完成后回收其资源。
进程池Pool
multiprocessing.Pool 类用于创建进程池,从而简化子进程的管理和任务分发。
可以通过 map()
和apply()
等方法将任务分发给进程池中的子进程执行。
import multiprocessing as mp
def func_execute(num):
data_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for i in data_list:
if i % num == 0:
print(num, "进程打印对应值", i)
if __name__ == '__main__':
pool = mp.Pool(processes=4)
# 分发任务
for i in range(2, 6):
pool.map(func_execute, (i,))
pool.close()
pool.join()
进程间通信
一般在使用多进程执行相互关联的任务过程中,进程间需要相互通信;
multiprocessing.Queue 类提供了队列用于子进程间通信,可以安全地在多个进程之间传递数据;
import multiprocessing as mp
def func_execute(queue_obj):
# 从队列中获取数据
data = queue_obj.get()
print("传入queue的值", data)
if __name__ == '__main__':
queue = mp.Queue()
process = mp.Process(target=func_execute, args=(queue,))
process.start()
queue.put("the long way rounds!")
multiprocessing.Pipe 类提供了进程间通信的管道;
import multiprocessing as mp
# 与 conn1, conn2 = mp.Pipe(duplex=True) 等效
conn1, conn2 = mp.Pipe()
其中,conn1
和 conn2
就是创建出来的两个连接对象,它们分别代表管道的两端;
在 Pipe 的 通信模式 为 全双工模式 时,conn1
和 conn2
都能作为发送端和接收端;
即conn1
和 conn2
各自的 send()
和 recv()
方法都可用。
Pipe 的通信模式,通过参数 duplex
控制,duplex
默认为True
。即默认情况下,Pipe 创建的管道是全双工模式;
如果希望某个端口只能进行发送操作,而另一个端口只能进行接收操作,可以设置 duplex
参数 为 False
;
这样 就只有 conn2
作为 发送端,conn1
作为接收端,发送端只能使用 send()
,接收端只能使用 recv()
,否则会报错;
import multiprocessing as mp
def func_execute(pipe):
conn1, conn2 = pipe
# 此处会报错 OSError: connection is write-only
# 因为 Pipe(duplex=False) conn2 是入口,只能用它发送值
msg = conn2.recv()
print("主进程发送的值", msg)
if __name__ == '__main__':
conn1, conn2 = mp.Pipe(duplex=False)
sub_proc = mp.Process(target=func_execute, args=((conn1, conn2),))
sub_proc.start()
# 此处会报错 OSError: connection is read-only
# 因为 Pipe(duplex=False) conn1 是出口,只能用它读取管道中的值
conn1.send("the long way rounds!")
sub_proc.join()
Pipe 创建的管道是基于fork机制的,所以在主进程创建Pipe的时候,Pipe的两个连接的都是主进程;
当主进程创建子进程后,也被拷贝了一份。此时子进程和主进程各自拥有两个连接对象;
我们可以选择保留或者关闭对应管道入口和出口的链接对象,让当前进程成为输入端和输出端;
同时,当连接同一个管道的所有链接关闭时,这个管道才会关闭;
示例
import multiprocessing as mp
def func_execute(pipe):
conn1, conn2 = pipe
# 关闭 conn1 将 conn2 作为唯一的出入口
conn1.close()
msg = conn2.recv()
print("主进程发送的值", msg)
if __name__ == '__main__':
conn1, conn2 = mp.Pipe()
sub_proc = mp.Process(target=func_execute, args=((conn1, conn2),))
sub_proc.start()
# 关闭 conn2 将 conn1 作为唯一的出入口
conn2.close()
conn1.send("the long way rounds!")
sub_proc.join()
共享内存
multiprocessing.Queue 和 multiprocessing.Pipe 一般是以发送和接收的方式共享进程间的数据,
但是这种方式不适合用在大多需要面向持续共享数据的场景中,同一份数据发送后,进程运行一段时间很难还原当时的状态;
所以 multiprocessing 提供了共享内存,在多个进程之间共享数据。
multiprocessing.Value 类用于在进程之间共享单个值,
multiprocessing.Array 类用于在进程之间共享数据数组。
import multiprocessing as mp
def func_execute(v, a):
print("修改之前", v.value, a[:])
v.value = 12
a[0] = 12
if __name__ == '__main__':
# 第一个参数表示值的数据类型。可用的数据类型包括:
# 'd'(双精度浮点数)
# 'b'(布尔)
# 'i'(有符号整数)
# 'f'(浮点数)
# 'c'(单字符)
# 'u'(Unicode字符)
val_value = mp.Value('d')
val_array = mp.Array("i", [1, 3, 4, 6])
sub_proc = mp.Process(target=func_execute, args=(val_value, val_array))
sub_proc.start()
sub_proc.join()
print("修改之后", val_value.value, val_array[:])
服务器进程
multiprocessing.Manager 类提供了一个服务器进程,用于管理共享状态和对象,从而在多个进程之间共享数据。
除了Value、Array,还有 list、dict、Namespace 等其他在多个进程之间共享的数据结构。
此外,单个管理器可以由网络上不同电脑上的进程共享。但是在电脑和电脑之前传递数据会比使用共享内存慢。
示例
import multiprocessing as mp
def func_execute(d, l):
print("修改之前", d, l)
d["v"] = 'the long way rounds.'
l.reverse()
if __name__ == '__main__':
with mp.Manager() as manager:
val_dict = manager.dict()
val_list = manager.list(range(10))
p = mp.Process(target=func_execute, args=(val_dict, val_list))
p.start()
p.join()
print("修改之后", val_dict, val_list)
同步机制
multiprocessing.Lock 类提供了简单的锁机制,用于确保在多个进程中对共享资源的互斥访问。 使用 acquire 方法获取锁,并使用
release 方法释放锁,以确保只有一个进程可以访问共享资源。
multiprocessing.Semaphore 类提供了信号量,用于限制同时访问某个资源的进程数量。 可以通过 acquire 方法来获取信号量,使用
release 方法释放信号量。
multiprocessing.Event 类用于进程间的事件通知和等待。可以使用 set 方法设置事件,使用 wait 方法等待事件。