Actor模式——Python实现及应用

“并发是一种解耦策略”      —— Robert C.Martin

何为Actor模式呢?

引用维基百科的解释 Actor Model

在计算机科学中,参与者模式(Actor model)是一种并发运算上的模型。“参与者”是一种程序上的抽象概念,被视为并发运算的基本单元:当一个参与者接收到一则消息,它可以做出一些决策、创建更多的参与者、发送更多的消息、决定要如何回答接下来的消息。

这段话很全面,但也需要我们花时间来消化与理解。不妨先试着阅读以下这段Python代码实现,再分析理解。

from queue import Queue
from threading import Thread


class ActorExit(Exception):
    pass


class Actor(object):
    def __init__(self):
        self._thread = Thread()
        self._queue = Queue()

    def send(self, msg):
        self._queue.put(msg)

    def receive(self):
        msg = self._queue.get()
        if msg is ActorExit:
            raise ActorExit()
        return msg

    def stop(self):
        if self._thread.is_alive():
            self.send(ActorExit)

    def start(self):
        if not self._thread.is_alive():
            self._thread = Thread(target=self._bootstrap)
            self._thread.start()

    def _bootstrap(self):
        try:
            self.run()
        except ActorExit:
            pass

    def run(self):
        while True:
            msg = self.receive()
            print(msg)


# Sample Actor
class PrintActor(Actor):
    def run(self):
        while True:
            msg = self.receive()
            print("Got:", msg)


if __name__ == '__main__':
    p = PrintActor()
    p.start()
    p.send("Hello")
    p.send("World")
    p.stop()

这里我们定义了Actor模式的基类Actor和其子类实现PrintActor

Actor对象是一个计算单元,所以每个actor都需要包含了一个Thread对象,用以执行给定的消息处理操作。同时actor对象也包含了一个Queue对象,实现了异步调用(在send消息后能立刻返回控制权)。
在启动actor之后,线程对象会执行引导函数_bootstrap,进而执行run函数。一般来说,在run函数里,我们会通过receive函数来读取之前通过send函数发送来的消息,然后根据给定的逻辑处理消息。特别地,receive函数会抛出ActorExit异常,而ActorExit异常正是我们作为停止线程的哨兵值。

Actor model 作为一种并发运算模型,其作用在于将 目的(做什么)和时机(何时做)解耦,以优化应用程序的吞吐量和结构。例如用户想要打印一段信息,那么他需要去调用对应的PrintActor的send函数(目的),然后程序会立刻返回,继续执行操作;至于这个打印的需求何时(时机)用户则无需关心。而这也是Actor模式吸引人之处——简单性:用户设计时,只需在其子类中定义好 run函数中的处理操作,使用时只需调用send函数,消息就能得到对应的处理。

抽象到一个新的高度,我们可以看到Actor模式在设计上有3个并发防御原则:

  1. 单一权责:每个actor只负责处理对应类型的消息,且其中的并发代码和其他组件的代码是分隔开的。
  2. 限制数据成员作用域:actor只开放send接口,最大程度地减小内部成员的暴露,限制可能被共享的数据的访问,缩减“临界区”范围。
  3. 线程之间独立:每个actor线程都只在自己的世界里存在,不与其他线程共享数据。线程之间唯一的交互是send出去的消息,所以,当消息是不可变对象 或者 actor存储的是消息副本时,就能保证两个线程之间是独立的。

妥善使用Actor模式,保证子类也符合这三个原则,那么程序在架构层面上,能很好地降低多线程应用程序设计的复杂度。

最后,我们发散一下思维。
这里PrintActor是最简单的一种Actor,实践中我们可以设计一个DatabaseActor,用来存储数据;设计一个LogActor,用来记录日志;甚至可以把send方法实现为在socket上的数据传输,将“发送信息”这一概念扩展到多进程甚至是分布式系统之中。

参考书籍:

  1. 《Python Cookbook》第三版
  2. 《代码整洁之道》Robert C.Martin

你可能感兴趣的:(Actor模式——Python实现及应用)