Python的多进程模块multiprocessing

众所周知,Python中不存在真正的多线程,Python中的多线程是一个并发过程。如果想要并行的执行程序,充分的利用cpu资源(cpu核心),还是需要使用多进程解决的。其中multiprocessing模块应该是Python中最常用的多进程模块了。

创建进程

基本上multiprocessing这个模块和threading这个模块用法是相同的,也是可以通过函数和类创建进程。

""" 案例1:函数式创建进程 """

import multiprocessing
import time


# 进程执行函数
def run(num):
    time.sleep(1)
    print(f'i am process{num}')


if __name__ == '__main__':
    # 获取开始的时间戳
    start = time.time()
    # 存放进程的列表,用于阻塞进程
    process_list = list()
    # 创建4个进程,并将每个创建好的进程对象放到process_list中
    for i in range(1, 5):
        process = multiprocessing.Process(target=run, args=(i,))
        # 启动该进程
        process.start()
        process_list.append(process)

    # 只有进程全部结束,再向下执行
    for j in process_list:
        j.join()

    # 结束的时间戳
    end = time.time()
    # 打印该程序运行了几秒
    print(end-start)

# i am process1
# i am process2
# i am process3
# i am process4
# 1.122110366821289

上述案例基本上就是笔者搬用了上篇文章多线程的案例,可见其使用的相似之处。导入multiprocessing后实例化Process就可以创建一个进程,参数的话也是和多线程一样,target放置进程执行函数,args存放该函数的参数。

""" 案例2:类继承创建进程 """

import multiprocessing
import time


# 继承Process,转变为进程类
class MyProcess(multiprocessing.Process):
    def __init__(self, process_id):
        # 必须实现Process类的init方法
        super().__init__()
        self.process_id = process_id

    # 重写执行函数
    def run(self):
        time.sleep(1)
        print(f'i am process{self.process_id}')


if __name__ == '__main__':
    # 获取开始的时间戳
    start = time.time()
    # 存放进程的列表,用于阻塞进程
    process_list = list()
    # 创建4个进程,并将每个创建好的进程对象放到process_list中
    for i in range(1, 5):
        process = MyProcess(i)
        # 启动该进程
        process.start()
        process_list.append(process)

    # 只有进程全部结束,再向下执行
    for j in process_list:
        j.join()

    # 结束的时间戳
    end = time.time()
    # 打印该程序运行了几秒
    print(end - start)

# i am process1
# i am process2
# i am process3
# i am process4
# 1.1184189319610596

使用类来创建进程也是需要先继承multiprocessing.Process并且实现其init方法。

进程池

Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求。

但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程。

""" 案例3:进程池Pool """

import multiprocessing
import time


# 进程执行函数
def func(msg):
    print('process start...', msg)
    time.sleep(3)
    print('process end...')


if __name__ == '__main__':
    print('ready~~~~~~~~~~~~~~~~~go~~~~~')

    # 创建进程池,最大进程数量为3
    pool = multiprocessing.Pool(processes=3)
    for i in range(5):
        msg = f'hello {i}'
        # 以非阻塞的方式,维持执行的进程总数为processes并在进程结束后自动添加新的进程
        pool.apply_async(func, (msg,))

    # 阻止后续任务提交到进程池
    pool.close()
    # 等待工作进程结束
    pool.join()

    print('game~~~~~~~~~~~~~~~~~over~~~~~')

# ready~~~~~~~~~~~~~~~~~go~~~~~
# process start... hello 0
# process start... hello 1
# process start... hello 2
# process end...
# process start... hello 3
# process end...
# process end...
# process end...
# game~~~~~~~~~~~~~~~~~over~~~~~

需要注意的是,在调用join方法阻塞进程前,需要先调用close方法,,否则程序会出错。

在上述案例中,提到了非阻塞,当把创建进程的方法换为pool.apply(func, (msg,))时,就会阻塞进程,出现下面的状况。

# ready~~~~~~~~~~~~~~~~~go~~~~~
# process start... hello 0
# process end...
# process start... hello 1
# process end...
# process start... hello 2
# process end...
# process start... hello 3
# process end...
# process start... hello 4
# process end...
# game~~~~~~~~~~~~~~~~~over~~~~~

进程队列

在multiprocessing模块中还存在Queue对象,这是一个进程的安全队列,近似queue.Queue。队列一般也是需要配合多线程或者多进程使用。

下列案例是一个使用进程队列实现的生产者消费者模式。

""" 案例4:基于进程队列的生产者消费者模式 """

import random
import time
import multiprocessing


# 生产者
def producer(queue):
    for i in range(10):
        time.sleep(random.randint(1, 3))
        res = f'物品{i}'
        # 入队
        queue.put(res)
        print(f'{multiprocessing.current_process().name}生产{res}')


# 消费者
def consumer(queue):
    while True:
        # 出队
        res = queue.get()
        time.sleep(random.randint(1, 3))
        print(f'{multiprocessing.current_process().name}消费{res}')


if __name__ == '__main__':
    # 生产消费队列
    queue = multiprocessing.Queue()
    # 创建生产进程
    process1 = multiprocessing.Process(target=producer, args=(queue,))
    # 进程名
    process1.name = 'process_1'

    # 创建消费进程
    process2 = multiprocessing.Process(target=consumer, args=(queue,))
    # 进程名
    process2.name = 'process_2'

    print('程序开始!!!')
    # 启动进程
    process1.start()
    process2.start()

# 程序开始!!!
# process_1生产物品0
# process_1生产物品1
# process_2消费物品0
# process_1生产物品2
# process_2消费物品1
# process_1生产物品3
# ......

管道

multiprocessing支持两种进程间的通信,其中一种便是上述案例的队列,另一种则称作管道。在官方文档的描述中,multiprocessing中的队列是基于管道实现的,并且拥有更高的读写效率。

管道可以理解为进程间的通道,使用Pipe([duplex])创建,并返回一个元组(conn1,conn2)。如果duplex被置为True(默认值),那么该管道是双向的,如果duplex被置为False,那么该管道是单向的,即conn1只能用于接收消息,而conn2仅能用于发送消息。

其中conn1、conn2表示管道两端的连接对象,每个连接对象都有send()和recv()方法。send和recv方法分别是发送和接受消息的方法。例如,可以调用conn1.send发送消息,conn1.recv接收消息。如果没有消息可接收,recv方法会一直阻塞。如果管道已经被关闭,那么recv方法会抛出EOFError。

""" 案例5:管道 """

import multiprocessing
import random
import time


# 发送数据
def proc_send(pipe, data):
    for d in data:
        pipe.send(d)
        print(f'{multiprocessing.current_process().name}发送了数据{d}')
        time.sleep(random.random())


# 接收数据
def proc_recv(pipe):
    while True:
        print(f'{multiprocessing.current_process().name}接收了数据{pipe.recv()}')
        time.sleep(random.random())


if __name__ == '__main__':
    # 创建管道
    pipe1, pipe2 = multiprocessing.Pipe()
    # 数据
    data = [i for i in range(10)]
    # 创建进程process1
    process1 = multiprocessing.Process(target=proc_send, args=(pipe1, data))
    process1.name = 'process_1'
    # 创建进程process1
    process2 = multiprocessing.Process(target=proc_recv, args=(pipe2,))
    process2.name = 'process_2'

    # 启动进程
    process1.start()
    process2.start()

    # 阻塞进程
    process1.join()
    process2.join()

# process_1发送了数据0
# process_2接收了数据0
# process_1发送了数据1
# process_2接收了数据1
# process_1发送了数据2
# process_2接收了数据2
# process_1发送了数据3
# process_2接收了数据3
# ......

关于multiprocessing模块其实还有很多实用的类和方法,由于篇幅有限(懒),笔者就先写到这里。该模块其实用起来很像threading模块,像锁对象和守护线程(进程)等multiprocessing模块也是有的,使用方法也近乎相同。

如果想要更加详细的了解multiprocessing模块,请参考官方文档。

# multiprocessing --- 基于进程的并行
https://docs.python.org/zh-cn/3/library/multiprocessing.html

你可能感兴趣的:(Python的多进程模块multiprocessing)