1 前置知识储备
自分布式计算出现以来,业界已经开始广泛研究基于消息传递编程模型的解决方案。关于消息传递,Wikipedia 描述其广泛定义主要包括:远程过程调用(Remote Procedure Calls, RPC) 和 消息传递接口(Message Passing Interface, MPI)。但是,如今我们所谈到的消息传递,通常是指 actor 模型(Actor Model)。作为一种通用的消息传递编程模型,其起源于 20 世纪 70 年代,如今被广泛用于构建大规模可伸缩分布式系统。
Actor 模型
一个 actor 定义为一个计算单元。所谓麻雀虽小,五脏俱全,每个 Actor 包含了存储、通信、计算等能力。在分布式系统中,通常包含了非常多的服务器集群,每一台服务器又包含了大量 actor 实例,它们共同构成了强大的并行计算能力。
Actor 的核心思想是 独立维护隔离状态,并基于消息传递实现异步通信。围绕其进行实现,actor 通常包含以下特征:
每个 actor 持有一个邮箱(mailbox),本质上是一个队列,用于存储消息。
每个 actor 可以发送消息至任何 actor。
每个 actor 可以通过处理消息来更新内部状态,对于外部而言,actor 的状态是隔离的状态(isolated state)。
为了便于通信,actor 模型使用 异步 消息传递。消息传递不使用任何中间实体,如:通道(channel)。由于 actor 模型的消息是异步传递的,中间可能会经过很长时间,甚至丢失,因此无法保证消息到达目标 actor 时的顺序。每个 actor 都完全独立于任何其他实例,actor 之间的交互完全基于异步消息,因此能够在很大程度上避免共享内存的存在问题。
任务调度
Actor 模型根据任务调度的方式可以分为两种,分别是:
(1)基于线程(thread-based)的 actor 模型
基于线程的 actor 模型,其本质是为每一个 actor 分配一个独立的“线程”。这里的“线程”并不是严格意义的操作系统线程,而是广泛意义的执行过程,它可以是线程、协程或虚拟机线程。
在基于线程的 actor 模型中,每个 actor 独占一个线程,如果当前 actor 的邮箱为空,actor 会阻塞当前线程,等待接收新的消息。在实现中,一般使用 receive 原语。
这种 actor 模型实现起来比较简单,但是缺点也非常明显,由于线程数量受到系统的限制,因此 actor 的数量也会受到限制。现阶段,只有少部分 actor 模型采用基于线程的实现方式,如:Erlang、Scala Actor、Cloud Haskell。
(2)事件驱动(event-driven)的 actor 模型
在事件驱动的 actor 模型,actor 并不直接与线程耦合,只有在事件触发(即接收消息)时,才为 actor 的任务分配线程并执行。这种方式使用续体闭包(Continuation Closure)来封装 actor 及其状态。当事件处理完毕,即退出线程。通过这种方式,我们可以使用很少的线程来执行大量 actor 产生的任务。在实现中,一般使用 react 原语。
事件驱动的 actor 模型在消息触发时,会自动创建并分配线程。在这种过程中,一般的优化是将 actor 执行建立在底层的线程池之上,这些线程可以是线程、协程或虚拟机线程。从概念上讲,这种实现与 run loop、event loop 机制非常相似。
2 LabVIEW的AF示例
假设有学生和老师这两个actor,学生有个“向老师交了一份儿作业”的方法;老师有“给学生的作业打分。”这个方法;
先来设计学生这个actor:
(1)新建一个空白的Project,并命名保存为“AF_student_teacher.lvproj”:
(2)右键新建操作者Actor——Student:
student这个类我们还定义一个“姓名”的私有私有(私有字段);并写一个其“写入”的方法:
(3)新建 静态模板方法——交作业
“交作业”方法体代码:
(4)为“交作业”方法创建 消息:
(5)测试“Student”Actor:
点击vi运行按钮: