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)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

smart_cat

你的鼓励将是我写作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值