Python多进程(multiprocessing模块)

1. multiprocessing

1.1. Process类

multiprocessing模块提供了一个Process类来代表一个进程对象
栗子:

from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Process(target=run_proc, args=('test',))
    print('Child process will start.')
    p.start()
    p.join()
    print('Child process end.')

执行结果如下:

Parent process 14322.
Child process start.
Run child process test(14323)...
child process end.

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例。用start()方法启动,join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

1.2. Pool类

如果要启动大量的子进程,可以用进程池的方式批量创建子进程。Pool类可以提供指定数量的进程供用户调用,当有新的请求提交到Pool中时,如果池还没有满,就会创建一个新的进程来执行请求。如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求。
栗子:

from multiprocessing import Pool
import os, time, random

def long_time_task(name):
    print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Pool(4)
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print('Waiting for all subprocesses done...')
    p.close()
    p.join()
    print('All subprocesses done.')

执行结果如下:

Parent process 14945.
Waiting for all subprocesses done...
Run task 0 (14946)...
Run task 1 (14947)...
Run task 2 (14948)...
Run task 3 (14949)...
Task 1 runs 0.07 seconds.
Run task 4 (14947)...
Task 4 runs 1.68 seconds.
Task 2 runs 2.07 seconds.
Task 3 runs 2.09 seconds.
Task 0 runs 2.59 seconds.
All subprocesses done

代码解读:
Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。请注意输出的结果,task 0,1,2,3是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的大小设置为了4.

子进程subprocess

很多时候,子进程并不是自身,而是一个外部进程subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。
下面的例子演示了如何在Python代码中运行命令nslookup www.baidu.com,这和命令行直接运行的效果是一样的:

import subprocess

print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])
print('Exit code:', r)

运行结果:

$ nslookup www.baidu.com
Server:		162.105.129.122
Address:	162.105.129.122#53

Non-authoritative answer:
www.baidu.com	canonical name = www.a.shifen.com.
Name:	www.a.shifen.com
Address: 119.75.217.26
Name:	www.a.shifen.com
Address: 119.75.217.109

Exit code: 0

如果子进程还需要输入,则可以通过communicate()方法输入.

进程间通信Queue(允许多个生产者和消费者)

操作系统提供了很多机制来实现进程间的通信。Python的`multiprocessing`模块包装了底层的机制,提供了`Queue`、`Pipes`等多种方式来交换数据。`Pipe()`(用于两个进程之间的连接)、队列`Queue`(允许多个生产者和消费者)。
以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
def write(q):
    print('Process to write: %s' % os.getpid())
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())

# 读数据进程执行的代码:
def read(q):
    print('Process to read: %s' % os.getpid())
    while True:
        value = q.get(True)
        print('Get %s from queue.' % value)

if __name__=='__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 启动子进程pr,读取:
    pr.start()
    # 等待pw结束:
    pw.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    pr.terminate()

运行结果如下:

Process to write: 15909
Put a to queue...
Process to read: 15910
Get a from queue.
Put b to queue...
Get b from queue.
Put c to queue...
Get c from queue.

multiprocessing使用通常queue.Emptyqueue.Full异常来发出超时信号。它们在multiprocessing命名空间中不可用,因此需要从中导入它们 queue
Demo2:

from multiprocessing import Queue, Process
import os, random, time
from queue import Empty as QueueEmpty

def getter(name,queue):
	print('Son process %s'% name)
	while True:
		try:
			value = queue.get(True,10)
			# Queue.get([block[, timeout]])
			# block为True,就是如果队列中无数据了。
            #   |—————— 若timeout默认是None,那么会一直等待下去。
            #   |—————— 若timeout设置了时间,那么会等待timeout秒后才会抛出Queue.Empty异常
            # block 为False,如果队列中无数据,就抛出queue.Empty异常
			print("Process %s get : %d"%(name,value))
		except QueueEmpty:
			break

def putter(name,queue):
	print("Son process %s"%name)
	for i in range(0,5):
		queue.put(i)
		# 放入数据 put(obj[, block[, timeout]])
        # 若block为True,如队列是满的:
        #  |—————— 若timeout是默认None,那么就会一直等下去
        #  |—————— 若timeout设置了等待时间,那么会等待timeout秒后,如果还是满的,那么就抛出Queue.Full.
        # 若block是False,如果队列满了,直接抛出queue.Full
		print('Process %s put: %d'%(name, i))
		time.sleep(random.random())

if __name__ == '__main__':
	q = Queue()
	getter_process = Process(target = getter,args = ('Getter',q))
	putter_process = Process(target = putter,args = ('Putter',q))
	getter_process.start()
	putter_process.start()

执行结果:

Son process Getter
Son process Putter
Process Putter put: 0
Process Getter get : 0
Process Putter put: 1
Process Getter get : 1
Process Putter put: 2
Process Getter get : 2
Process Putter put: 3
Process Getter get : 3
Process Putter put: 4
Process Getter get : 4

进程间通信Pipe(用于两个进程之间的连接)

Pipe常用来在两个进程间通信,两个进程分别位于管道的两端。

multiprocessing.Pipe([duplex])
(con1, con2) = Pipe() 
# con1管道的一端,负责存储,也可以理解为发送信息
# con2管道的另一端,负责读取,也可以理解为接受信息

示例(注意Pipe()构造函数返回的是来两个Pipe实例):

from multiprocessing import Process, Pipe

def send(pipe):
    pipe.send(['spam'] + [42, 'egg'])   # send 传输一个列表
    pipe.close()

if __name__ == '__main__':
    (con1, con2) = Pipe()                            # 创建两个 Pipe 实例
    sender = Process(target=send, args=(con1, ))     # 函数的参数,args 一定是实例化之后的 Pipe 变量,不能直接写 args=(Pip(),)
    sender.start()                                   # Process 类启动进程
    print("con2 got: %s" % con2.recv())              # 管道的另一端 con2 从send收到消息
    con2.close()                                     # 关闭管道

执行结果:

con2 got: ['spam', 42, 'egg']

管道是可以同时发送和接受消息的(半双工):

from multiprocessing import Process, Pipe

def talk(pipe):
    pipe.send(dict(name='Bob', spam=42))            # 传输一个字典
    reply = pipe.recv()                             # 接收传输的数据
    print('talker got:', reply)

if __name__ == '__main__':
    (parentEnd, childEnd) = Pipe()                  # 创建两个 Pipe() 实例,也可以改成 conf1, conf2
    child = Process(target=talk, args=(childEnd,))  # 创建一个 Process 进程,名称为 child
    child.start()                                   # 启动进程
    print('parent got:', parentEnd.recv())          # parentEnd 是一个 Pipe() 管道,可以接收 child Process 进程传输的数据
    parentEnd.send({x * 2 for x in 'spam'})         # parentEnd 是一个 Pipe() 管道,可以使用 send 方法来传输数据
    child.join()                                    # 传输的数据被 talk 函数内的 pip 管道接收,并赋值给 reply
    print('parent exit')

执行结果:

parent got: {'name': 'Bob', 'spam': 42}
talker got: {'ss', 'aa', 'pp', 'mm'}
parent exit
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值