multiprocessing模块用法与实例

读书笔记

《Python并行编程实战》 第3章 基于进程的并行

multiprocessing

该模块是Python语言标准库的一部分,用于实现基于进程的并行。

1 理解multiprocessing模块

要求子进程能够进入main模块,即’__main__’

2 创建进程

2.1 准备工作

创建进程的步骤:

  1. 定义Process对象
  2. 调用进程的start()方法运行这个进程
  3. 调用进程的join()方法。它会等待,直到这个进程完成任务然后退出。

2.2 实现

import multiprocessing

def myFunc(i):
    print("calling myFunc from process n : %s" % i)
    for j in range(0,i):
        print('output from myFunc is : %s' %j)

if __name__ == '__main__':
    for i in range(6):
        process = multiprocessing.Process(target = myFunc, args=(i,))
        process.start()
        process.join()

如果没有join方法,子进程不会结束,必须手动杀死。

2.3 输出与原理

calling myFunc from process n : 0
calling myFunc from process n : 1
output from myFunc is : 0
calling myFunc from process n : 2
output from myFunc is : 0
output from myFunc is : 1
calling myFunc from process n : 3
output from myFunc is : 0
output from myFunc is : 1
output from myFunc is : 2
calling myFunc from process n : 4
output from myFunc is : 0
output from myFunc is : 1
output from myFunc is : 2
output from myFunc is : 3
calling myFunc from process n : 5
output from myFunc is : 0
output from myFunc is : 1
output from myFunc is : 2
output from myFunc is : 3
output from myFunc is : 4
  • Process创建的参数主要是函数myFunc和要传入的参数args。
  • 调用process.start()开始进程
  • 调用process.join()会等待直到子进程结束才继续执行主进程
  • 为什么一定要有main模块?因为在执行进程时会无限次导入函数所在脚本,如果不用main模块,则会无限递归调用。解决这个问题的另一个方法是把子进程与主进程写在不同的文件中。

3 命名进程

3.1 准备工作

可以用multiprocessing.current_process()来访问正在运行的进程的一些属性。name属性来标识其名字。

3.2 实现

import multiprocessing
import time

def myFunc():
    name = multiprocessing.current_process().name
    print("Starting process name = %s\n" % name)
    time.sleep(3)
    print("Exiting process name = %s" % name)

if __name__ == '__main__':
    process_with_name = multiprocessing.Process(name='myFunc process', target=myFunc)
    process_without_default_name = multiprocessing.Process(target=myFunc)
    process_with_name.start()
    process_without_default_name.start()

    # 主进程阻塞直到子进程完成
    process_with_name.join()
    process_without_default_name.join()

3.3 输出

Starting process name = myFunc process
Starting process name = Process-2


Exiting process name = Process-2
Exiting process name = myFunc process
  • 可以在创建进程时使用name来传递自定义的名字
  • 可以使用multiprocessing.current_process()来访问当前进程的属性

4 守护进程(daemon)

4.1 准备工作

守护进程即后台运行的进程,将process.daemon赋值为True即可在后台运行进程。

4.2 实现

import multiprocessing
import time

def foo():
    name = multiprocessing.current_process().name
    print('Starting %s \n' % name)
    if name=='background_process':
        for i in range(5):
            print('--> %d\n' %i)
            time.sleep(1)
    else:
        for i in range(5,10):
            print('--> %d\n' %i)
            time.sleep(1)

    print('Exiting %s\n' %name)

if __name__ == '__main__':
    background_proess = multiprocessing.Process\
        (name='background_process',
        target=foo)
    background_proess.daemon = True
    
    No_background_process = multiprocessing.Process\
        (name='No_background_process',
        target=foo)
    No_background_process.daemon = False
    background_proess.start()
    No_background_process.start()

4.3 输出

Starting No_background_process

--> 5

--> 6

--> 7

--> 8

--> 9

Exiting No_background_process
  • 可以看到后台进程没有任何输出
  • 可以在创建process后,用process.daemon=True来将其设为后台进程

5 杀死进程

5.1 准备工作

没有完美的软件,杀死一个进程总是有必要的。

  • 可以使用terminate方法立即杀死一个进程
  • 可以使用is_alive方法看进程是否活着

5.2 实现

import multiprocessing
import time

def foo():
    print('starting function')
    for i in range(0,10):
        print('-->%d\n' % i)
        time.sleep(1)
    print('Finished function')

if __name__ == '__main__':
    p = multiprocessing.Process(target=foo)
    print('Process before execution:', p, p.is_alive())
    p.start()
    print('Process running:', p, p.is_alive())
    p.terminate()
    print('Process terminated:', p, p.is_alive())
    p.join()
    print('Process joined:', p, p.is_alive())
    print('Process exit code:', p.exitcode)

5.3 输出

Process before execution: <Process name='Process-1' parent=13404 initial> False
Process running: <Process name='Process-1' pid=2592 parent=13404 started> True
Process terminated: <Process name='Process-1' pid=2592 parent=13404 started> True
Process joined: <Process name='Process-1' pid=2592 parent=13404 stopped exitcode=-SIGTERM> False
Process exit code: -15
  • 做的事情:创建一个进程,然后杀死它,等待结束后,查看进程的exit code
  • exit code的规则
    • == 0 :没有产生任何错误
    • 0 :进程有一个错误,并退出这个代码

    • < 0 :进程由一个-1 * ExitCode信号杀死,这个信号是一个正值

6 子类中定义进程

6.1 准备工作

为了实现一个multiprocessing定制子类,需要完成以下工作:

  • 定义multiprocessing.Process的一个子类,重定义run()方法。
  • 覆盖__init__(self[, args])方法来增加额外的参数(如果需要)。
  • 覆盖run(self[,args])方法来实现启动进程时Process需要做的工作。
    创建Process子类后,可以创建一个实例,然后调用start方法,会自动调用其run方法。

6.2 实现

import multiprocessing
class MyProcess(multiprocessing.Process):

    def run(self):
        print('called run method by %s' % self.name)

if __name__ == '__main__':
    for i in range(10):
        process = MyProcess()
        process.start()
        process.join()

6.3 输出

called run method by MyProcess-1
called run method by MyProcess-2
called run method by MyProcess-3
called run method by MyProcess-4
called run method by MyProcess-5
called run method by MyProcess-6
called run method by MyProcess-7
called run method by MyProcess-8
called run method by MyProcess-9
called run method by MyProcess-10
  • 可以实现multiprocessing.Process的子类,覆盖__init__方法和run方法

7 使用队列交换数据

队列是一种先进先出(FIFO,First In First Out)的数据结构,就像排队一样。

7.1 准备工作

以经典的生产者/消费者问题来练习。生产者生产产品,消费者消费产品,有一个公共的缓冲区用来存放产品。需要实现生产者/消费者对缓冲区的互斥访问和先生产后消费的同步。这里就不着重讲这个问题了,这是操作系统的一个经典问题。

7.2 实现

这里的实现并没有涉及信号量,按理说要实现对队列的互斥访问。

import multiprocessing
import random
import time

class producer(multiprocessing.Process):
    def __init__(self, queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue
    
    def run(self):
        for i in range(5):
            item = random.randint(0,256)
            self.queue.put(item)
            print('Process Producer : item %d appended to queue by %s' % (item, self.name))
            time.sleep(1)
            print('The size of queue is %s' % self.queue.qsize())

class consumer(multiprocessing.Process):
    def __init__(self, queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue
    
    def run(self):
        while True:
            if self.queue.empty():
                print('the queue is empty')
                break
            else:
                time.sleep(2)
                item = self.queue.get()
                print('Process Consumer : item %d popped from queue by %s\n' % (item, self.name))
            time.sleep(1)

if __name__ == '__main__':
    queue = multiprocessing.Queue()
    process_producer = producer(queue)
    process_consumer = consumer(queue)
    process_producer.start()
    time.sleep(1)
    process_consumer.start()

    process_producer.join()
    process_consumer.join()

7.3 输出

Process Producer : item 23 appended to queue by producer-1
The size of queue is 1
Process Producer : item 18 appended to queue by producer-1
The size of queue is 2
Process Producer : item 0 appended to queue by producer-1
Process Consumer : item 23 popped from queue by consumer-2
The size of queue is 2
Process Producer : item 3 appended to queue by producer-1
The size of queue is 3
Process Producer : item 36 appended to queue by producer-1
The size of queue is 4
Process Consumer : item 18 popped from queue by consumer-2
Process Consumer : item 0 popped from queue by consumer-2
Process Consumer : item 3 popped from queue by consumer-2
Process Consumer : item 36 popped from queue by consumer-2
the queue is empty
  • 使用multiprocessing.Queue()来传递信号
  • queue.put(item)入队
  • queue.get(item)出队

8 使用管道交换对象

8.1 准备工作

管道(pipe)是什么?管道连接两个进程,通过接收/发送来实现进程间通信。

  • multiprocessing.Pipe(duplex)可以实例化一个管道,返回一个对象对(conn1,conn2),表示管道的两端。
  • duplex = True表示管道双向
  • duplex = False表示管道单向,conn1只用于接收,conn2只用于发送

8.2 实现

进程1创建item 0~9数字送入Pipe1,进程2从Pipe1接收数字,将其平方后送入Pipe2。最后从Pipe2接收。

import multiprocessing
from venv import create

def create_items(pipe):
    output_pip, _ = pipe
    for item in range(10):
        output_pip.send(item)
    output_pip.close()

def multiply_items(pipe_1, pipe_2):
    close, input_pip = pipe_1
    close.close()
    output_pipe, _ = pipe_2
    try:
        while True:
            item = input_pip.recv()
            output_pipe.send(item*item) # 返回各个管道元素的乘积,即平方
    except EOFError:
        output_pipe.close()

if __name__ == '__main__':
    pipe_1 = multiprocessing.Pipe(True)
    process_pipe_1 = multiprocessing.Process(target=create_items, args=(pipe_1,))
    process_pipe_1.start()
    # 进程1创建0~9的数字,送入pipe1

    pipe_2 = multiprocessing.Pipe(True)
    process_pipe_2 = multiprocessing.Process(target=multiply_items, args=(pipe_1,pipe_2,))
    process_pipe_2.start()
    # 进程2接收pipe1的数字,并将其平方后送入pipe2

    # 关闭两个管道
    pipe_1[0].close()
    pipe_2[0].close()

    # 打印结果
    try:
        while True:
            print(pipe_2[1].recv())
    except EOFError:
        print("End")

8.3 输出

0
1
4
9
16
25
36
49
64
81
End
  • 为什么conn需要调用close?因为如果不这样的话,recv的连接就会一致阻塞,等待接收,不会出现EOF。
  • 使用Pipe会比Queue更快,因为Queue建立在Pipe之上。Queue常用于多进程间的通信。

9 同步进程

为什么需要同步?因为多个进程一起工作时,有时需要严格保证执行顺序,否则会造成错误或无法预知的结果。
进程的同步原语与threading库中的同步原语非常相似。如下:

  • Lock:这个对象可以是锁定或非锁定状态。Lock对象有两个方法acquire()和release()来管理对同一个共享资源的访问。
  • Event:用来实现进程间的简单通信;一个进程通知一个事件,另一个进程等待这个通知。Event对象有两个方法set()和clear()来管理它自己的内部标志。
  • Condition:用来同步一个工作流的各部分。它有两个基本方法:wait()用来等待一个条件,notify_all()用来通知所应用的条件。
  • Semaphore:用来共享一个公共资源,例如可以同时支持固定数目的连接。
  • RLock:这定义了重入锁对象。
  • Barrier:这个对象将程序划分为阶段,要求所有进程都达到屏障才能继续。屏障之后执行的代码不能与屏障之前的代码并发运行。

9.1 准备工作

Python中的屏障(Barrier)用来等待固定数目的进程执行完成,然后给定的进程才能继续执行。这里是一个用屏障实现同步的例子。

9.2 实现

# 用barrier来实现进程同步
import multiprocessing
from multiprocessing import Barrier, Lock, Process
from time import time
from datetime import datetime

def test_with_barrier(synchronizer, serializer):
    name = multiprocessing.current_process().name
    synchronizer.wait()
    now = time()
    with serializer:
        print('process %s ----> %s' % (name, datetime.fromtimestamp(now)))

def test_without_barrier():
    name = multiprocessing.current_process().name
    now = time()
    print('process %s ----> %s' % (name, datetime.fromtimestamp(now)))

if __name__ == '__main__':
    synchronizer = Barrier(2)
    serializer = Lock()
    Process(name='p1 - test_with_barrier', target=test_with_barrier, args=(synchronizer, serializer,)).start()
    Process(name='p2 - test_with_barrier', target=test_with_barrier, args=(synchronizer, serializer,)).start()
    Process(name='p3 - test_without_barrier', target=test_without_barrier).start()
    Process(name='p4 - test_without_barrier', target=test_without_barrier).start()

9.3 输出

process p3 - test_without_barrier ----> 2022-01-21 14:10:45.443280
process p4 - test_without_barrier ----> 2022-01-21 14:10:45.453279
process p2 - test_with_barrier ----> 2022-01-21 14:10:45.473277
process p1 - test_with_barrier ----> 2022-01-21 14:10:45.473277

在9.2的代码中,Barrier实现了这样的功能:等两个进程都到达指定位置,然后一起继续前进,所以p1和p2才打印了相同的时间戳。

10 使用进程池

利用进程池机制,在多个输入值上执行的一个函数可以并行化,将输入数据分布到多个进程,实现数据级并行(data parallelism)

10.1 准备工作

multiprocessing.Pool类可以完成简单的并行处理任务。
Pool类有以下方法:

  • apply(): 这会阻塞,直到结果就绪。
  • apply_async():apply()的一个变体,实现了异步,即主进程不会等待所有子进程运行结束。
  • map():内置map函数的并行版本。这个方法会阻塞,直到结果就绪,它将可迭代处理的数据划分为多个块,作为单独的任务提交到进程池。
  • map_async():这是map()的一个变形,会返回一个结果对象。如果指定了回调,应当可以调用这个回调,这要接受一个参数。结果就绪时,会调用一个回调。回调应当立即完成,否则处理结果的进程会阻塞。

10.2 实现

import multiprocessing
import time
def function_square(data):
    x = data*data
    x = 2*x+9
    x = x*x
    x = x-1000
    x = x*6
    x = x*x
    x = x/78
    x = x*2.98 
    x = x*0.492
    return x

if __name__ == '__main__':
    inputs = list(range(0,1000000))
    pool = multiprocessing.Pool(processes=8)
    t0 = time.time()
    pool_outputs = pool.map(function_square, inputs)
    t1 = time.time()
    t2 = time.time()
    outputs = list(map(function_square, inputs))
    #time.sleep(1)
    t3 = time.time()
    pool.close()
    pool.join()

    print('pool: %.12f   |   no_pool: %.12f' % (t1-t0, t3-t2))

10.3 输出

pool: 0.280999183655   |   no_pool: 0.480020523071

总结一下就是多进程地进行map,然而额外开销很大,简单运算的情况下还是直接map要快得多。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Python中,multiprocessing模块提供了一种用于创建和管理多个进程的方法,以实现并行计算的目的。其中,multi函数是multiprocessing模块的一个重要函数之一,用于创建多个进程并并发地执行任务。 multi函数的使用方法如下: 首先,在代码中导入multiprocessing模块:import multiprocessing 接下来,定义一个函数来作为子进程的任务,该函数会在多个进程中并发执行。例如,我们可以定义一个计算平方的函数: def square(x): return x * x 然后,使用multi函数来创建多个进程并执行任务: if __name__ == '__main__': # 创建一个进程池,指定最大进程数为4 pool = multiprocessing.Pool(processes=4) # 使用进程池的map函数并发地执行square函数,传入参数为[1, 2, 3, 4, 5] result = pool.map(square, [1, 2, 3, 4, 5]) # 输出结果 print(result) 在上述代码中,首先创建了一个进程池,通过multiprocessing.Pool(processes=4)指定了最大进程数为4。然后,使用进程池的map函数并发地执行任务函数square,并传入参数为[1, 2, 3, 4, 5]。最后,通过print函数输出执行结果。 运行以上代码,将会得到[1, 4, 9, 16, 25]这样一个输出结果,即计算每个数字的平方。 总结来说,Python中multi函数通过创建多个进程来实现并发执行任务。使用multi函数需要导入multiprocessing模块,并配合进程池的map函数来并发地执行任务函数。以上是multi函数的使用方法,在实际应用中可以根据需要进行进一步的优化和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值