和线程不同,进程不支持托管对象。尽管可以像前面所述那样可以创建共享值和数组,但这对更高级的python对象(如字典、列表、用户自定义对象等)而言不起作用。但是multiprocessing模块确实提供了一种使用共享对象的途径,但前提是它们运行在所谓的管理器的控制之下、管理器是独立的子进程,其中存在真实的对象,并以服务器的形式运行。其他进程通过使用代理访问共享对象,这些代理作为服务器的客户端运行。
使用简单托管对象的最直观方式是使用Manager()函数。
Manager()
在一个单独的进程中创建运行的管理器。返回值是SyncMsanager类型的实例,SyncManager类型定义在multiprocessing.managers模块中。
Manager()函数返回的SyncManager的实例m具有一系列方法,可用于创建共享对象并返回用于访问这些共享对象的代理。通常,可以创建一个管理器,并在启动任何新进程之前使用这些方法创建共享对象。
下面的例子说明了如何使用管理器创建一个在进程之间共享的字典
import multiprocessing
import time# 只要设定要传递的事件,就打印d
def watch(d, evt):while True:evt.wait()print(d)evt.clear()if __name__ == '__main__':m = multiprocessing.Manager()d = m.dict() # 创建一个shared dictevt = m.Event() # 创建一个shared event# 启动监视字典的程序p = multiprocessing.Process(target=watch, args=(d, evt))p.daemon = Truep.start()# 更新字典并通知监视者d['foo'] = 42time.sleep(5)evt.set()time.sleep(5)# 终止进程和管理器p.terminate()m.shutdown()
如果需要其他类型的共享对象,比如用户自定义类的实例,则必须创建自定义管理对象。为此,需要创建一个继承自BaseManager类的类,BaseManager类定义在multiprocessing.managers子模块中。
managers.BaseManager([address [, authkey]])
为用户定义对象创建管理器服务器的基类。address是一个可选元组(hostname, port), 用于指定服务器的网络地址。如果省略次参数,操作系统将简单分配一个对应于某些空闲端口号的地址。authkey是一个字符串,用于对连接到服务器的客户端进行身份验证。如果省略此参数,将使用current_process().authkey的值。
如果mgrclass是一个继承自BaseManager的类,可以使用以下类方法来创建返回代理给共享对象的方法。
mgrclass.register(typeid [, callable [, proxytype [, exposed [, method_to_typeid [, create_method]]]]])
使用管理器类注册一种新的数据类型。
从BaseManager类派生的管理器的实例m必须手动启动才能运行。相关属性和方法如下:
下面的例子说明了如何为用户自定义类型创建管理器, 在代理上是无法访问特殊方法和以下划线开头的所有方法的,在更高级的应用程序中,可以自定义代理,从而进行更仔细的控制访问。这是通过定义继承自BaseProxy的类来实现的, 以下代码说明如何为A类自定义代理,从而正确地公开__iadd__方法,并使用属性(property)公开x特性(attribute):
import multiprocessing
from multiprocessing.managers import BaseManager
from multiprocessing.managers import BaseProxyclass A(object):def __init__(self, value) -> None:self.x = valuedef __repr__(self):return "A(%s)" % self.xdef getX(self):return self.xdef setX(self, value):self.x = valuedef __iadd__(self, value):self.x += valuereturn selfclass AProxy(BaseProxy):# referent上公开的所有方法列表_exposed_ = ['__iadd__', 'getX', 'setX']# 实现代理的公共接口def __iadd_proxy__(self, value):self._callmethod('__iadd__', (value, ))return self@propertydef x(self):return self._callmethod('getX', ())@x.setterdef x(self, value):self._callmethod('setX', (value, ))class MyManager(BaseManager):pass
MyManager.register("A", A, proxytype=AProxy)if __name__ == '__main__':m = MyManager()m.start()a = m.A(11)print(a.x)a.x = 22print(a.x)a.__iadd_proxy__(33)print(a.x)