Python并发编程之托管对象
一、什么是托管对象
和线程不同,进程不支持托管对象。尽管可以像前面所述那样可以创建共享值和数组,但这对更高级的python对象(如字典、列表、用户自定义对象等)而言不起作用。但是multiprocessing模块确实提供了一种使用共享对象的途径,但前提是它们运行在所谓的管理器的控制之下、管理器是独立的子进程,其中存在真实的对象,并以服务器的形式运行。其他进程通过使用代理访问共享对象,这些代理作为服务器的客户端运行。
使用简单托管对象的最直观方式是使用Manager()函数。
Manager()
在一个单独的进程中创建运行的管理器。返回值是SyncMsanager类型的实例,SyncManager类型定义在multiprocessing.managers模块中。
Manager()函数返回的SyncManager的实例m具有一系列方法,可用于创建共享对象并返回用于访问这些共享对象的代理。通常,可以创建一个管理器,并在启动任何新进程之前使用这些方法创建共享对象。
- m.Array(typecode, sequence)
- m.BoundedSemaphore([value])
- m.Condition([lock])
- m.dict([args])
- m.Event()
- m.list([sequence])
- m.Lock()
- m.Namespace()
- m.Queue()
- m.RLock()
- m.Semaphore()
- m.Value(typecode, value)
二、托管对象示例代码
下面的例子说明了如何使用管理器创建一个在进程之间共享的字典
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 dict
evt = m.Event() # 创建一个shared event
# 启动监视字典的程序
p = multiprocessing.Process(target=watch, args=(d, evt))
p.daemon = True
p.start()
# 更新字典并通知监视者
d['foo'] = 42
time.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]]]]])
使用管理器类注册一种新的数据类型。
- typeid是一个字符串,用于命名特定类型的共享对象。这个字符串应该是有效的python标识符。
- callable是创建或返回要共享的实例的可调用对象。
- proxytype是一个类,负责提供客户端中要使用的代理对象的实现。通常,这些类是默认生成的,因此一般置为none
- exposed是共享对象上方法名称的序列,它将会公开给代理对象。如果省略此参数,将使用proxytype._exposed_的值,如果proxytype._exposed_未定义,序列将包含所有的公共方法。
- method_to_typeid 是从方法名称到类型IDS的映射。如果它为none,将使用proxytype._method_to_typeid_的值。
- create_method是一个布尔标志,用于指定是否在mgrclass中创建名为typeid的方法。默认值是true
从BaseManager类派生的管理器的实例m必须手动启动才能运行。相关属性和方法如下:
- m.address
- m.connect()
- m.serve_forever()
- m.shutdown()
- m.start()
四、自定义托管对象示例代码
下面的例子说明了如何为用户自定义类型创建管理器, 在代理上是无法访问特殊方法和以下划线开头的所有方法的,在更高级的应用程序中,可以自定义代理,从而进行更仔细的控制访问。这是通过定义继承自BaseProxy的类来实现的, 以下代码说明如何为A类自定义代理,从而正确地公开__iadd__方法,并使用属性(property)公开x特性(attribute):
import multiprocessing
from multiprocessing.managers import BaseManager
from multiprocessing.managers import BaseProxy
class A(object):
def __init__(self, value) -> None:
self.x = value
def __repr__(self):
return "A(%s)" % self.x
def getX(self):
return self.x
def setX(self, value):
self.x = value
def __iadd__(self, value):
self.x += value
return self
class AProxy(BaseProxy):
# referent上公开的所有方法列表
_exposed_ = ['__iadd__', 'getX', 'setX']
# 实现代理的公共接口
def __iadd_proxy__(self, value):
self._callmethod('__iadd__', (value, ))
return self
@property
def x(self):
return self._callmethod('getX', ())
@x.setter
def 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 = 22
print(a.x)
a.__iadd_proxy__(33)
print(a.x)