演员模型pykka①-快速开始

快速开始

Pykka是演员模型的Python实现,演员模型在每个执行单元间引入一些简单的规则去控制共享的状态和协助,这样能更容易的构建并发的应用。

演员模型的规则

  • 一个演员是与其他演员并发执行的测试单元(演员模型之间是并发的)

  • 一个演员不和其他演员共享状态,它拥有自己的状态(演员直接是独立的个体)

  • 一个演员只能通过发送和接收信息来与其他演员交流,而且它只能发送信息给它有地址的演员

  • 当一个演员收到信息后,也许他会做出以下行为:

    • 改变它自己的状态,例如:改变状态后才能对未来收到的信息做出反应

    • 发送信息给其他的演员,或者

    • 开始新的演员

​ 没有任何演员是必须的,他们可能被应用于任何顺序

  • 一个演员同一时刻只能处理一条消息,换句话说,一个单例演员不能给你任何并发的操作,也不需要使用内部锁来保护自己的状态

演员的实现

Pykka的演员API伴遵循以下实现

线程:每个ThreadingActor围绕一个规则的线程执行,就是threading.Thread,作为处理未来结果的句柄,它使用一个围绕queue.Queue队列的小型装饰器ThreadingFuture,它并不依赖于Python之外的包,ThreadingActor能稳定运行和非演员的线程。

Pykka 2 和更早的版本附带了一些在 Pykka 3 中删除的替代实现:

  • gevent: 每个演员执行通过一个gevent库(gevent 是一个基于协程的Python网络库,它使用 greenlet在libevlibuv事件循环之上提供高级同步 API)
  • Eventlet: 每个演员的执行通过Eventlet库(Eventlet 是一个用于 Python 的并发网络库,它允许您更改运行代码的方式,而不是编写代码的方式)

一个基础的演员

在最基本的形式中,Pykka actor 是一个带有 on_receive()方法的类:

import pykka

class Greeter(pykka.ThreadingActor):
    def on_receive(self, message):
        print('Hi there!')

要启动一个actor,您调用类的方法start(),该方法启动actor并返回一个actor引用,该引用可用于与正在运行的actor进行通信:

actor_ref = Greeter.start()

如果您需要在创建时将参数传递给actor,您可以将它们传递给start()方法,并使用常规 __init__()方法接收它们:

import pykka

class Greeter(pykka.ThreadingActor):
    def __init__(self, greeting='Hi there!'):
        super().__init__()
        self.greeting = greeting

    def on_receive(self, message):
        print(self.greeting)

actor_ref = Greeter.start(greeting='Hi you!')

知道init函数在启动演员模型执行上下文中是非常有用的。
还有一些钩子用于在演员模型自己的执行上下文中运行代码,当actor启动时,当actor停止时,以及当一个未处理的异常被引发时。

停止演员模型时,你应该调用stop方法

actor_ref.stop()

如果演员想自己停止自己,也是调用stop方法

self.stop

如果演员一旦被停止,它就不能再重新启动

发送信息

发送信息给演员,你可以使用tell方法或者ask方法,tell方法不等答复就会发送信息,换句话说,它从不会阻塞,ask方法默认会永远阻塞等待回复,如果提供timeout给ask,则可以指定它等待回复的时间,如果你需要回复,但是不是立刻需要它,因为你可能要首先去做其他事情,你可以设置block=Flase,这样ask方法则会立刻返回一个未来对象

信息对象可以是任意类型,例如一个字典或者你的自定义信息类【低版本的只能是字典】

actor_ref.tell('Hi!')
# => Returns nothing. Will never block.
不阻塞,不返回任何东西

answer = actor_ref.ask('Hi?')
# => May block forever waiting for an answer
也许会永远阻塞等待回答

answer = actor_ref.ask('Hi?', timeout=3)
# => May wait 3s for an answer, then raises exception if no answer.
等待三秒,三秒内获取不到返回则抛出异常

future = actor_ref.ask('Hi?', block=False)
# => Will return a future object immediately.
将会直接返回一个未来对象
answer = future.get()
# => May block forever waiting for an answer
获取未来对象的信息,永远阻塞
answer = future.get(timeout=0.1)
# => May wait 0.1s for an answer, then raises exception if no answer.
等待0.1秒获取信息,接收不到会抛出异常

处于性能原因,在你发送信息到接收者之前Pykka不会克隆信息,您自己负责使用不可变数据结构,或者复制.deepcopy()发送给其他参与者的数据。

回应一个信息

如果一个信息是通过使用ask方法发送的,你可以回应发送者一个简单的值,通过使用on_receive方法

import pykka

class Greeter(pykka.ThreadingActor):
    def on_receive(self, message):
        return 'Hi there!'

actor_ref = Greeter.start()

answer = actor_ref.ask('Hi?')
print(answer)
# => 'Hi there!'

None是一个有效的响应,所以如果你None显式返回,或者根本不返回,一个包含的响应None将被返回给发送者。

从接收者的角度来看,它不关心信息的发送方式是tell或者ask,当发送者不期望返回时,on_receive返回的值将会被忽略。

针对异常的处理情况是类似这样的:当ask方法使用后,如果你在on_receive方法抛出一个异常,这个异常会传递给发送者。

演员代理

伴随着演员和分支提供的基础建立块,我们得到了一些更高级的抽象,pykka提供一个单一抽象在演员模型之上,称之为演员代理,你可以不使用演员代理,但是我们发现它很方便当创建Mopidy(Mopidy 是一个用 Python 编写的可扩展音乐服务器)时。

代理对象将会使用反射来区分出演员的公共属性和方法,然后反射出演员的完整API,一些保护的属性或者下划线开头的方法会被忽略,这正是Python的特点

当我们使用代理访问属性或者调用方法时,它将会调用演员的属性和方法,然后返回结果给我们,结果是被包装在Future对象中,所以必须使用get方法去获取实际的数据

由于参与者一次只处理一条消息并且所有消息都按顺序保存,因此您不需要添加调用来get() 阻止处理,直到参与者完成处理您的最后一条消息

在底层,这个代理做一些事情通过使用ask方法发送信息给演员,通过这种方式保持了演员模型的限制,这种唯一的魔术是一些基础的自我检查和自动建立的三种不同类型的消息,一是调用方法,另一个是阅读属性,最后一个是写属性

# encoding=utf-8
import pykka


class Calculator(pykka.ThreadingActor):
    def __init__(self):
        super().__init__()
        self.last_result = None

    def add(self, a, b=None):
        if b is not None:
            self.last_result = a + b
        else:
            self.last_result += a
        return self.last_result

    def sub(self, a, b=None):
        if b is not None:
            self.last_result = a - b
        else:
            self.last_result -= a
        return self.last_result


if __name__ == '__main__':
    actor_ref = Calculator.start()
    proxy = actor_ref.proxy()  # 获取代理对象
    future = proxy.add(1, 2)  # 调用演员的方法
    print(type(future))  # 获取方法的结果,包装在future对象中
    print(future.get())  # 通过get方法获取实际的数据
    print(proxy.last_result.get())  # 也可以这么获取
    proxy.add(3)
    proxy.add(2)
    proxy.add(1)
    proxy.sub(4)
    print(type(future))  # 获取方法的结果,包装在future对象中
    print(future.get())  # 通过get方法获取实际的数据
    print(proxy.last_result.get())  # 也可以这么获取

    # proxy.last_result = 20
    print(proxy.last_result.get())  # 也可以这么获取

代理上的可遍历性

有时您将希望通过代理访问Actor属性的方法或属性。对于这种情况,Pykka支持“遍历属性”。通过将Actor属性标记为遍历,Pykka在访问时不会返回属性,但在返回的新代理中将其包装在一起。

要将属性标记为遍历,只需使用traversealbe()函数

import pykka

class AnActor(pykka.ThreadingActor):
    playback = pykka.traversable(Playback())

class Playback(object):
    def play(self):
        return True

proxy = AnActor.start().proxy()
play_success = proxy.playback.play().get()

您可以访问与您一样深的方法和属性,只要Actor与末尾的方法或属性之间的路径上的所有属性都被标记为遍历。

posted @ 2022-02-14 19:07  南风丶轻语  阅读(424)  评论(0编辑  收藏  举报