multiprocessing.managers

在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。

Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。由于managers模块封装很好,不必了解网络通信的细节,就可以很容易地编写分布式多进程程序。

Server process

Manager()返回一个manager对象。它控制一个服务器进程,这个进程会管理Python对象并允许其他进程通过代理的方式来操作这些对象。

manager对象支持多种类型。例子见下:

from multiprocessing importProcess, Managerdeff(d, l):
d[1] = "1"d["2"] = 2d[0.25] =None
l.reverse()if __name__ == '__main__':
with Manager() as manager:
d=manager.dict()     #产生一个代理对象d
l= manager.list(range(10))
p= Process(target=f, args=(d,l))
p.start()
p.join()print(d)print(l)

解释:

with语句:见这篇文章

with 语句是从 Python 2.5 开始引入的一种与异常处理相关的功能(2.5 版本中要通过 from __future__ import with_statement 导入后才可以使用),从 2.6 版本开始缺省可用(参考 What's new in Python 2.6? 中 with 语句相关部分介绍)。

with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取acquire和release等。

⚠️,with语句的实现类似try..finally。

指向其他共享对象的对象。

共享对象也可以说是代理 指涉 的对象。

多个代理对象可能指向同一个指涉对象。

代理对象代理了指涉对象的一系列方法调用(虽然并不是指涉对象的每个方法都有必要被代理)。通过这种方式,代理的使用方法可以和它的指涉对象一样:

>>> from multiprocessing import Manager >>> manager = Manager() >>> l = manager.list([i*i for i in range(10)]) >>> print(l) [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> print(repr(l)) >>> l[4] 16 >>> l[2:5] [4, 9, 16]

上面使用了list(), dict()方法

管理器的特点:

服务器进程管理器比使用共享内存对象更灵活,它们支持二进制对象类型。

同时,一个单独的manager可以被网络上的不同计算机的进程共享。

缺点是比使用shared memory慢。

使用manager对象可以创建一个共享queue。具体见下一章节:

Managers

Managers提供了创建一种数据的方法,这个数据可以被不同的进程共享。这种共享也包括通过网络在不同计算机的进程上共享。

multiprocessing.Manager()

返回一个已启动的SyncManager对象(BaseManager的子类的实例对象),用于在进程之间分享数据。

SyncManager对象(点击查看方法)对应一个已经启动的子进程,它拥有一系列方法,可以为大部分常用数据类型创建并返回 代理对象 代理,用于进程间同步。甚至包括共享列表和字典。(👆的代码例子)

当管理器被垃圾回收或者父进程退出时,管理器进程会立即退出。

classmultiprocessing.managers.BaseManager([address[, authkey]])

创建一个BaseManager对象。创建后,需要调用start()或get_server().server_forever()确保对象对于的管理器进程已经启动。

address参数,管理器服务进程监听的地址。如果值是None,则任意主机的请求都能建立连接。

authkey参数,byte类的字符串。认真标识(验证码)

start(), 为管理器开启子进程。

get_server(),返回一个Server对象。

connect(), 连接本地管理器对象到一个远程管理器进程

shutdown() 停止管理器的进程。配合start()。

register(typid, callable)⚠️最重要的类方法,凡是注册到管理器的类型/对象,就可以被网络上的不同进程共享了。

例子

下面是一个简单的Master/Worker模型,实现一个简单的分布计算。如果要启动多个worker,就可以把任务分配到多台机器上了,

比如把计算n*n的代码替换成发送邮件,就实现了邮件队列的异步发送。

通过manager模块的支持,多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个进程中。

⚠️

注意Queue的作用是用来传递任务和接收结果,每个任务的描述数据量要尽量小。

比如发送一个处理日志文件的任务,就不要发送几百兆的日志文件本身,而是发送日志文件存放的完整路径,由Worker进程再去共享的磁盘上读取文件。

会报告2个错误。

第一个❌

#_pickle.PicklingError: Can't pickle at 0x107ef8670>: attribute lookup on __main__ failed

第二个❌

RuntimeError:

An attempt has been made to start a new process before the

current process has finished its bootstrapping phase.

This probably means that you arenotusing fork to start your

child processesandyou have forgotten to use the proper idiominthe main module:if __name__ == '__main__':

freeze_support()

...

看文档https://docs.python.org/zh-cn/3/library/multiprocessing.html, 3.8版本增加了freeze_support()函数。主要是为了支持windows可执行文件。毕竟multiprocessing可用于分布式进程。

所以必须引入freeze_support:

看代码:

服务器上的代码:

importrandom, time, queuefrom multiprocessing.managers importBaseManagerfrom multiprocessing importfreeze_support#建立2个队列,一个发送,一个接收
task_queue =queue.Queue()
result_queue=queue.Queue()defget_task():returntask_queuedefget_result():returnresult_queueclass QueueManager(BaseManager): pass
#服务器的管理器上注册2个共享队列
QueueManager.register('get_task', callable=get_task)
QueueManager.register('get_result', callable=get_result)#设置端口,地址默认为空。验证码authkey需要设定。
manager = QueueManager(address=('', 5000), authkey=b'abc')defmanager_run():
manager.start()#通过管理器访问共享队列。
task =manager.get_task()
result=manager.get_result()#对队列进行操作, 往task队列放进任务。
for value in range(10):
n= random.randint(0,100)print('Put task %d' %n)
task.put(n)#从result队列取出结果
print('Try get result...')try:for value in range(10):
r= result.get(timeout=10)print('Result: %s' %r)exceptqueue.Empty:print('result is empty')#关闭管理器。
manager.shutdown()print('master exit.')if __name__ == '__main__':
freeze_support()
manager_run()

另一台机器(或本机启动也可以):

importtime, sys, queuefrom multiprocessing.managers importBaseManagerclass QueueManager(BaseManager): pass

#从网络上的服务器上获取Queue,所以注册时只提供服务器上管理器注册的队列的名字:

QueueManager.register('get_task')
QueueManager.register('get_result')
server_addr= '127.0.0.1'
print('Connect to server %s...' %server_addr)#b'abc'相当于'abc'.encode('ascii'),类型是bytes
m = QueueManager(address=(server_addr, 5000), authkey=b'abc')#连接服务器
m.connect()#获得服务器上的队列对象
task =m.get_task()
result=m.get_result()for value in range(10):try:
n= task.get(timeout=1)print('run task %d * %d...' %(n, n))
r= '%d * %d = %d' % (n , n, n*n)
time.sleep(1)
result.put(r)exceptqueue.Empty:print('task queue is empty')print('worker exit.')