Python进程专题7:托管对象

上一篇文章: Python进程专题6:共享数据与同步
下一篇文章: Python进程专题8:分布集群的消息传递
进程不支持共享对象,上面描述的建立共享值和数组,但都是指定的特殊类型,对高级的Python对象(如:字典、列表、用户自定义类的实例)不起做用。

还好multiprocessing模块提供了一种使用共享对象的途径:单前提是这些对象运行在所谓的【管理器】的控制之下。segmentfault

管理器是独立的子进程,其中存在真实的对象,并以服务器的形式运行,其余进程经过使用代理访问共享对象,这些数组

代理做为管理器服务器的客户端而运行。服务器

语法:

m=Manager():在一个独立的进程中建立运行的管理器,返回值是SyncManager(定义在multiprocess.managersa模块中)类的实例。
m能够建立共享对象

经常使用方法

  • m.Array(typecode,sequence)
在服务器上建立共享的Array实例并返回能够访问它的代理。
  • m.BoundedSemaphore(value)
在服务器上建立共享的threading.BoundedSemphore实例,并返回可访问它的代理。
  • m.Condition(lock)
在服务器上建立共享的threading.Condition实例,并返回可访问它的代理。
lock是m.Lock()或m.Rlock()方法建立的代理实例
  • m.dict(args)
在服务器上建立共享的dict(字典)实例,并返回能够访问它的代理。
  • m.Event()
在服务器上建立共享的thread.Event实例,并返回能够访问它的代理。
  • m.list(sequence)
在服务器上建立共享的list实例,并返回能够访问它的代理。
  • m.Lock
子服务器上建立共享的threading.Lock实例,并返回可访问它的代理。
  • m.Namespace()
在服务器建立共享的namespace对象,并返回能够访问它的代理。
namespace就是一个相似于Python模块的对象。例如:若是n是一个命名空间代理,那么可使用(.)赋值和读取属性,
例如:n.name=value 或value=n.name。但name的名称十分重要,若是name以字母开头,那么这个值就是管理器所拥有的共享对象的一部分,
全部其它进程都可访问它,若是name如下划线开头,那么它只是代理对象的一部分,并非共享。
  • m.Queue()
在服务器上建立共享的Queue.Queue对象,并返回可访问它的代理。
  • m.RLock()
在服务器上建立共享的threading.Rlick对象,并返回可访问它的代理。
  • m.Semaphore(value)
在服务器上建立共享的threading.Semaphore对象,并返回可访问它的代理。
  • m.value(typecode,value)
在服务器上建立一个共享的Value对象,并返回可访问它的代理。
  • 实例1使用管理者建立一个在进程中共享的字典:
代码:
#使用管理器建立一个在进程间共享的字典

import multiprocessing
import time

def watch(dict,event):
    while True:
        event.wait()
        print(dict)
        event.clear()


if __name__=="__main__":
    #实例化一个管理器
    m=multiprocessing.Manager()
    #在服务器上建立一个共享的字典,并返回能够调用它的代理
    dict=m.dict()
    #在服务器上建立一个共享的Event
    event=m.Event()

    #启动监视字典的进程
    p=multiprocessing.Process(target=watch,args=(dict,event,))
    #设置为后台进程,随主进程的销毁而销毁,该后台进程没法建立新的线程
    p.daemon=True
    p.start()

    #更新字典并通知监听者
    dict["name"]="mark"
    event.set()
    time.sleep(3)

    # 更新字典并通知监听者
    dict["age"] = 18
    event.set()
    time.sleep(3)


    #终止进程和管理器
    p.terminate()

    m.shutdown()

运行结果:网络

图片描述

继承BaseManager类,实现自定义对象的共享

若是想要共享用户自定义类的实例,则必须建立自定义管理器对象,为此,须要建立一个继承自BaseManager类的类,

其中BaseManager类定义在multiprocessing.managers子模块中。函数

  • 语法:
managers.BaseManager(address,authkey):用户自定对象建立管理器服务器的基类。
address是一个可选的元组(hostname,port),用于指定服务器的网络地址。若是忽略此参数,操做系统将简单
的分配一个对应于某些空闲端口号的地址。
authkey是一个字符串,用于对链接到服务器的客户端身份进行验证。若是忽略此参数,将是哟个current_process().authkey的值。

假设myclass是一个继承自BaseManager的类,可使用如下类方法来建立用于返回代理给共享对象的方法。spa

myclass.register(typeid,callable,proxytype,exposed,method_to_typeid,create_method):使用管理器类注册一种新的数据类型。

typeid:一个字符串,用于命名特殊类型的共享对象。这个字符串应该是有效的Python标识符。

callable:建立或返回要共享的实例的可调用对象。

proxytype:一个类,负责提供客户端中药使用的代理对象的实现,一般这些类是默认生成的,所以通常设置为None。
exposed:共享对象上方法名的序列,它将会公开给代理对象,若是省略此参数,将使用proxytype_exposed的值,
若是proxytype_expose未定义,序列将包含全部的公共方法(全部不如下划线_开头的可调用方法)。

method_to_typeid:从方法名称到类型IDS的映射,这里的IDS用于指定哪些方法应该使用代理对象返回他们的结果,
若是某个方法在这个映射中找不到,将复制和返回它的返回值,若是method_to_typeid为None,将使用proxytype_method_to_typeid_的值。

create_method:一个布尔标志,用于指定是否在mgclass中建立名为typeid的方法,默认值为True。

从BaseManager类派生的管理器的实例m必须手动启动才能运行。操作系统

  • 属性于经常使用方法
属性或者方法 解释说明
m.address 元组(hostname,port),表明管理器服务器正在使用的地址。
m.connect() 链接带远程管理器对象,该对象的地址在BaseManager构造函数中支出。
m.serve_forever() 在当前进程中运行管理器服务器。
m.shutdown() 关闭由start()启动的管理器服务器。
m.start() 启动一个单的子进程,并在该子进程中启动管理器服务器。

继承自BaseProxy的类的实例具备如下方法:线程

proxy._callmethod(name,args,kwargs):调用代理引用对象上的方法name。

proxy._getvalue():返回调用者中引用对象的副本,若是此次调用是在另外一个进程中,
那么引用对象将被序列化,发送给调用者,而后再进行反序列化。若是没法序列号对象将引起异常。

代码:代理

#继承BaseManager类,实现自定义对象的共享,利用代理公开属性与方法
import multiprocessing
from multiprocessing.managers import  BaseManager
from multiprocessing.managers import  BaseProxy

#普通须要共享的类,代理没法经过(.)这种形式直接访问属性
class MyClass():
    def __init__(self,value):
        self.name=value

    def __repr__(self):
        return "MyClass(%s)"%self.name

    def getName(self):
        return self.name
    def setName(self,value):
        self.name=value

    def __add__(self,valuye):
        self.name+=valuye
        return self

#经过自定义继承BaseProxy来实现代理,从而正确的公开__add__方法,并使用特性(property)公开name属性。
#BaseProxy来来自multiprocessing.managers模块
class MyClassProxy(BaseProxy):
    #referent上公开的全部方法列表
    _exxposed_=['__add__','getName','setName']
    #实现代理的公共结接口
    def __add__(self, value):
        self._callmethod('__add__',(value,))
        return self
    @property
    def name(self):
        return self._callmethod('getName',())
    @name.setter
    def name(self,value):
        self._callmethod('setName',(value,))


    def __repr__(self):
        return "MyClass(%s)"%self.name

    def getName(self):
        return self.name
    def setName(self,value):
        self.name=value

    def __add__(self,valuye):
        self.name+=valuye
        return self

class MyManager(BaseManager):
    pass

#不使用代理
#MyManager.register("MyClass",MyClass)
#使用代理
MyManager.register("MyClass",MyClass,proxytype=MyClassProxy)

if __name__=="__main__":
    m=MyManager()
    m.start()

    #建立托管对象,此时知识建立了一个实例代理,没法直接访问属性,必须使用函数来访问
    #代理没法访问特殊方法和下划线(_)开头的全部方法。
    a=m.MyClass("mark")
    print(a)
    print(a.getName())

    #不使用代理,下面两条语句会异常
    a.__add__("帅哥")
    print(a.name)

    #print(a.name)
    #a.__add__("帅哥")

结果:code

MyClass(mark)
mark
mark帅哥
相关文章
相关标签/搜索