背景
在之前的章节中,我们见识了如何用线程实现并发的应用。本章节将会介绍基于进程的并行。本章的重点将会集中在Python的 multiprocessing 和 mpi4py 这两个模块上。
multiprocessing 是Python标准库中的模块,实现了共享内存机制,也就是说,可以让运行在不同处理器核心的进程能读取共享内存。
mpi4py 库实现了消息传递的编程范例(设计模式)。简单来说,就是进程之间不靠任何共享信息来进行通讯(也叫做shared nothing),所有的交流都通过传递信息代替。
这方面与使用共享内存通讯,通过加锁或类似机制实现互斥的技术行成对比。在信息传递的代码中,进程通过 send() 和 receive 进行交流。
产生一个进程
“产生”(spawn)的意思是, 由父进程创建子进程。 父进程既可以在产生子进程之后继续异步执行, 也可以暂停等待子进程创建完成之后再继续执行。Python 的多进程可以通过下面的步骤创建:
1 创建进程对象
2 调用start 方法, 开启进程活动
3 调用join()方法, 在进程结束之前一直等待
import multiprocessing
def foo(i):
print ('called function in process: %s' %i)
return
if __name__ == '__main__':
Process_jobs = []
for i in range(5):
p = multiprocessing.Process(target=foo, args=(i,))
Process_jobs.append(p)
p.start()
p.join()
如果没有 join() ,主进程退出之后子进程会留在idle中,你必须手动杀死它们。
注意
子进程创建的时候需要导入包含目标函数的脚本。通过在 main 代码块中实例化进程对象,我们可以预防无限递归调用。最佳实践是在不同的脚本文件中定义目标函数,然后导入进来使用。所以上面的代码可以修改为:
import multiprocessing
import target_function
if __name__ == '__main__':
Process_jobs = []
for i in range(5):
p = multiprocessing.Process(target=target_function.function,args=(i,))
Process_jobs.append(p)
p.start()
p.join()
为一个进程命名
import multiprocessing
import time
def foo():
name = multiprocessing.current_process().name
print("Starting %s \n" % name)
time.sleep(3)
print("Exiting %s \n" % name)
if __name__ == '__main__':
process_with_name = multiprocessing.Process(name='foo_process', target=foo)
process_with_name.daemon = True # 注意原代码有这一行,但是译者发现删掉这一行才能得到正确输出
process_with_default_name = multiprocessing.Process(target=foo)
process_with_name.start()
process_with_default_name.start()
在后台运行一个进程
如果需要处理比较巨大的任务,又不需要人为干预,将其作为后台进程执行是个非常常用的编程模型。此进程又可以和其他进程并发执行。通过Python的multiprocessing模块的后台进程选项,我们可以让进程在后台运行。
import multiprocessing
import time
def foo():
name = multiprocessing.current_process().name
print("Starting %s " % name)
time.sleep(3)
print("Exiting %s " % name)
if __name__ == '__main__':
background_process = multiprocessing.Process(name='background_process', target=foo)
background_process.daemon = True
NO_background_process = multiprocessing.Process(name='NO_background_process', target=foo)
NO_background_process.daemon = False
background_process.start()
NO_background_process.start()
运行结果
start name no_process
exiting no_process
在非后台运行的进程会看到一个输出,后台运行的没有输出,后台运行进程在主进程结束之后会自动结束。
但是如果加join() 方法之后就不能 在后台运行啦也是会打印出来的。线程中 join 所完成的工作就是线程同步, 即主要线程任务结束之后, 进入阻塞状态, 一直等到其他的子线程执行结束之后, 主线程终止。
要想使用后台进行不能添加join(). join()作用是 阻塞当前线程, 意思是 主线程只有在join调用的线程结束才往下处理。 这个线程处理不完没有结束, 主线程也不能结束
使用多进程的常规方法是,先依次调用start启动进程,再依次调用join要求主进程等待子进程的结束
python多进程的理解 multiprocessing Process join run
时间:2014-09-21 16:34:02 阅读:11202 评论:0 收藏:0 [点我收藏+]
标签:class style 代码 src 使用 方法 http si .net
最近看了下多进程。
一种接近底层的实现方法是使用 os.fork()方法,fork出子进程。但是这样做事有局限性的。比如windows的os模块里面没有 fork() 方法。
windows:bubuko.com,布布扣。linux:bubuko.com,布布扣
另外还有一个模块:subprocess。这个没整过,但从vamei的博客里看到说也同样有局限性。
所以直接说主角吧 — multiprocessing模块。 multiprocessing模块会在windows上时模拟出fork的效果,可以实现跨平台,所以大多数都使用multiprocessing。
下面给一段简单的代码,演示一下创建进程:
encoding:utf-8
from multiprocessing import Process
import os, time, random
线程启动后实际执行的代码块
def r1(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())
def r2(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())
if name == “main“:
print “main process run…”
p1 = Process(target=r1, args=(‘process_name1‘, )) #target:指定进程执行的函数,args:该函数的参数,需要使用tuple
p2 = Process(target=r2, args=(‘process_name2‘, ))
p1.start() #通过调用start方法启动进程,跟线程差不多。
p2.start() #但run方法在哪呢?待会说。。。
p1.join() #join方法也很有意思,寻思了一下午,终于理解了。待会演示。
p2.join()
print "main process runned all lines..."
执行结果:
bubuko.com,布布扣
上面提到了两个方法:run 和join
run:如果在创建Process对象的时候不指定target,那么就会默认执行Process的run方法:
encoding:utf-8
from multiprocessing import Process
import os, time, random
def r():
print ‘run method‘
if name == “main“:
print “main process run…”
#没有指定Process的targt
p1 = Process()
p2 = Process()
#如果在创建Process时不指定target,那么执行时没有任何效果。因为默认的run方法是判断如果不指定target,那就什么都不做
#所以这里手动改变了run方法
p1.run = r
p2.run = r
p1.start()
p2.start()
p1.join()
p2.join()
print "main process runned all lines..."
另:python源码里,Process.run方法:
bubuko.com,布布扣
执行结果:
bubuko.com,布布扣
可见如果在实例化Process时不指定target,就会执行默认的run方法。
还有一个join方法:
最上面演示的代码中,在调用Process的start方法后,调用了两次join方法。这个join方法是干什么的呢?
官方文档的意思是:阻塞当前进程,直到调用join方法的那个进程执行完,再继续执行当前进程。
比如还是刚才的代码,只是把两个join注释掉了:
encoding:utf-8
from multiprocessing import Process
import os, time, random
def r1(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())
def r2(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())
if name == “main“:
print “main process run…”
p1 = Process(target=r1, args=(‘process_name1‘, ))
p2 = Process(target=r2, args=(‘process_name2‘, ))
p1.start()
p2.start()
#p1.join()
#p2.join()
print "main process runned all lines..."
执行结果:
bubuko.com,布布扣
发现主进程不像之前那样,等待两个子进程执行完了,才继续执行。而是启动两个进程后立即向下执行。
为了深刻理解,这次把p2的执行函数里面睡眠时间调大,让他多睡一会,然后保留p1的join,注释掉p2的join,效果更明显:
encoding:utf-8
from multiprocessing import Process
import os, time, random
def r1(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())
def r2(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random()*2)
if name == “main“:
print “main process run…”
p1 = Process(target=r1, args=(‘process_name1‘, ))
p2 = Process(target=r2, args=(‘process_name2‘, ))
p1.start()
p2.start()
p1.join()
#p2.join()
print "main process runned all lines..."
执行结果:
bubuko.com,布布扣
发现主线程只是等待p1完成了,就会向下执行,而不会等待p2是否完成。
所以使用多进程的常规方法是,先依次调用start启动进程,再依次调用join要求主进程等待子进程的结束。
然而为什么要先依次调用start再调用join,而不是start完了就调用join呢
p1.start()
p1.join()
p2.start()
join是用来阻塞当前线程的,p1.start()之后,p1就提示主线程,需要等待p1结束才向下执行,那主线程就乖乖的等着啦,自然没有执行p2.start()这一句
后台进程不允许创建子进程。否则,当后台进程跟随父进程退出的时候,子进程会变成孤儿进程。另外,它们并不是Unix的守护进程或服务(daemons or services),所以当非后台进程退出,它们会被终结。
杀死一个进程
可以使用 terminate() 方法立即杀死一个进程。另外,我们可以使用 is_alive() 方法来判断一个进程是否还存活。
创建一个目标函数为 foo() 的进程。启动之后,我们通过 terminate(0 方法杀死它。
# 杀死一个进程
import multiprocessing
import time
def foo():
print('Starting function')
time.sleep(0.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)
我们创建了一个线程,然后用 is_alive() 方法监控它的声明周期。然后通过调用 terminate() 方法结束进程。
最后,我们通过读进程的 ExitCode 状态码(status code)验证进程已经结束, ExitCode 可能的值如下:
== 0: 没有错误正常退出
0: 进程有错误,并以此状态码退出
< 0: 进程被 -1 * 的信号杀死并以此作为 ExitCode 退出
在我们的例子中,输出的 ExitCode 是 -15 。负数表示子进程被数字为15的信号杀死
如何在子类中使用进程
# -*- coding: utf-8 -*-
# 自定义子类进程
import multiprocessing
class MyProcess(multiprocessing.Process):
def run(self):
print ('called run method in process: %s' % self.name)
return
if __name__ == '__main__':
jobs = []
for i in range(5):
p = MyProcess()
jobs.append(p)
p.start()
p.join()
运行结果
called run method in process: MyProcess-1
called run method in process: MyProcess-2
called run method in process: MyProcess-3
called run method in process: MyProcess-4
called run method in process: MyProcess-5
实现一个自定义的进程子类,需要以下三步:
定义 Process 的子类
覆盖 __init__(self [,args]) 方法来添加额外的参数
覆盖 run(self, [.args]) 方法来实现 Process 启动的时候执行的任务
创建 Porcess 子类之后,你可以创建它的实例并通过 start() 方法启动它,启动之后会运行 run() 方法。
在进程之间交换对象
并行通常要在程序中交换数据,Multiprocessing 库 有两个Communication Channel 可以交换对象。:队列和管道
使用队列交换对象
我们可以通过队列数据结构来共享对象。
Queue 返回一个进程共享的队列,是线程安全的,也是进程安全的。任何可序列化的对象(Python通过 pickable 模块序列化对象)都可以通过它进行交换。
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(10):
item = random.randint(0, 256)
self.queue.put(item)
print("Process Producer : item %d appended to queue %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 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()
process_consumer.start()
process_producer.join()
process_consumer.join()
更多相关
- 队列还有一个 JoinaleQueue 子类,它有以下两个额外的方法:
task_done(): 此方法意味着之前入队的一个任务已经完成,比如, get() 方法从队列取回item之后调用。所以此方法只能被队列的消费者调用。 - join(): 此方法将进程阻塞,直到队列中的item全部被取出并执行。
task_done(): 此方法意味着之前入队的一个任务已经完成,比如, get() 方法从队列取回item之后调用。所以此方法只能被队列的消费者调用。
join(): 此方法将进程阻塞,直到队列中的item全部被取出并执行。
使用管道交换对象
Communication Channel是管道。
管道可以做以下事情:
- 返回一对被管道连接的连接对象
- 然后对象就有了 send /recieve 方法在进程之间通信
import multiprocessing
def create_items(pipe):
output_pipe, _ = pipe
for item in range(10):
output_pipe.send(item)
output_pipe.close()
def multiply_items(pipe_1, pipe_2):
close, input_pipe = pipe_1
close.close()
output_pipe, _ = pipe_2
try:
while True:
item = input_pipe.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()
# 第二个进程管道接收数字并计算
pipe_2 = multiprocessing.Pipe(True)
process_pipe_2 = multiprocessing.Process(target=multiply_items, args=(pipe_1, pipe_2,))
process_pipe_2.start()
pipe_1[0].close()
pipe_2[0].close()
try:
while True:
print(pipe_2[1].recv())
except EOFError:
print("End")
运行结果
0
1
4
9
16
25
36
49
64
81
End
进程间同步
多个进程可以协同工作来完成一项任务。通常需要共享数据。所以在多进程之间保持数据的一致性就很重要了。需要共享数据协同的进程必须以适当的策略来读写数据。相关的同步原语和线程的库很类似。
进程的同步原语如下:
- Lock: 这个对象可以有两种装填:锁住的(locked)和没锁住的(unlocked)。一个Lock对象有两个方法, acquire() 和 release() ,来控制共享数据的读写权限。
- Event: 实现了进程间的简单通讯,一个进程发事件的信号,另一个进程等待事件的信号。 Event 对象有两个方法, set() 和 clear() ,来管理自己内部的变量。
- Condition: 此对象用来同步部分工作流程,在并行的进程中,有两个基本的方法: wait() 用来等待进程, notify_all() 用来通知所有等待此条件的进程。
- Semaphore: 用来共享资源,例如,支持固定数量的共享连接。
- Rlock: 递归锁对象。其用途和方法同 Threading 模块一样。
- Barrier: 将程序分成几个阶段,适用于有些进程必须在某些特定进程之后执行。处于障碍(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()
process p3 - test_without_barrier ----> 2018-07-06 14:17:20.416147
process p4 - test_without_barrier ----> 2018-07-06 14:17:20.422132
process p2 - test_with_barrier ----> 2018-07-06 14:17:20.432129
process p1 - test_with_barrier ----> 2018-07-06 14:17:20.432129
p1 p2 相同
Barrier 类设置了一个线程数量障碍, 当等待的线程到达这个数量的时候就会唤醒所有等待线程
init(self, parties, action=None, timeout=None) parties障碍要求的线程数量 action设置了的话会在突破障碍的时候被某一个被唤醒的线程调用 timeout给之后的wait()设置了个默认的等待时间
wait(self, timeout=None) 当前线程进入阻塞状态
abort(self) 强行突破阻碍,所有正在等待的线程和要调用wait()方法的线程收到一个BrokenBarrierError异常
reset(self) 重置当前对象,所有正在等待的线程收到一个BrokenBarrierError异常
如何在进程之间管理状态
python 多进程模块提供了在所有的用户间管理共享信息的管理者(mananger). 一个管理者对象控制着持有python 对象的服务进程, 并允许其他进程操作共享对象。
1 它控制着管理共享对象的服务进程
2 它确保当某个进程修改了共享对象之后, 所有的进程拿到的共享对象都得到更新。
看个进程之间共享对象的例子
1 首先,程序创建了一个管理者的字典,在 n 个 taskWorkers 之间共享,每个worker更新字典的某一个index。
2 所有的worker完成之后,新的列表打印到 stdout :
所有的 进程都是操作这个 manage.
import multiprocessing
def worker(dictionary, key, item):
dictionary[key] = item
print("key = %d value = %d" % (key, item))
if __name__ == '__main__':
mgr = multiprocessing.Manager()
dictionary = mgr.dict()
jobs = [multiprocessing.Process(target=worker, args=(dictionary, i, i*2)) for i in range(10)]
for j in jobs:
j.start()
for j in jobs:
j.join()
print('Results:', dictionary)
key = 2 value = 4
key = 5 value = 10
key = 6 value = 12
key = 3 value = 6
key = 7 value = 14
key = 1 value = 2
key = 0 value = 0
key = 4 value = 8
key = 8 value = 16
key = 9 value = 18
Results: {2: 4, 5: 10, 6: 12, 3: 6, 7: 14, 1: 2, 0: 0, 4: 8, 8: 16, 9: 18}
都在操作这个 字典, 最后这个字典的值就是所有人操作的结果
使用进程池
- apply(): 直到得到结果之前一直阻塞。
- apply_async(): 这是 apply() 方法的一个变体,返回的是一个result对象。这是一个异步的操作,在所有的子类执行之前不会锁住主进程。
- map(): 这是内置的 map() 函数的并行版本。在得到结果之前一直阻塞,此方法将可迭代的数据的每一个元素作为进程池的一个任务来执行。
- map_async(): 这是 map() 方法的一个变体,返回一个result对象。如果指定了回调函数,回调函数应该是callable的,并且只接受一个参数。当result准备好时会自动调用回调函数(除非调用失败)。回调函数应该立即完成,否则,持有result的进程将被阻塞
创建了有4个进程的进程池,然后使用 map() 方法进行一个简单的计算。
import multiprocessing
def function_square(data):
result = data*data
return result
if __name__ == '__main__':
inputs = list(range(10))
pool = multiprocessing.Pool(processes=4)
pool_outputs = pool.map(function_square, inputs)
pool.close()
pool.join()
print ('Pool :', pool_outputs)
运行结果
Pool : [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
mpi4py 模块
进程线程中的join()方法理解
http://www.bubuko.com/infodetail-374280.html