【摸鱼笔记】multiprocessing,多进程并行更高效

背景

软件开发过程中,有时会需要程序同时处理多个任务,又或者需要程序处理的某一个任务各个部分不相互排队;

这时,我们会使用多线程、多进程等方式,让程序进行并行计算;

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()

其中,conn1conn2 就是创建出来的两个连接对象,它们分别代表管道的两端;

在 Pipe 的 通信模式 为 全双工模式 时,conn1conn2 都能作为发送端和接收端;

conn1conn2 各自的 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 方法等待事件。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

The_Singing_Towers

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值