看Erlang中Actor模型的执行方式和优劣

开发 开发工具
Erlang基于Actor模型实现,甚至可以说,没有Erlang在业界竖立的丰碑,Actor模型便不会如此受人关注。本文介绍了Erlang中Actor模型的执行方式和优劣。

Actor模型为并行而生。由于现在单台机器中独立的计算单元也越来越多,Actor模型的重要性也越来越大。Actor模型的理念非常简单:天下万物皆为Actor,Actor之间通过发送消息进行通信。不同的Actor可以同时处理各自的消息,从而获得了大规模的并发能力。

Erlang基于Actor模型实现,我们甚至可以这样认为,没有Erlang在业界竖立的丰碑,Actor模型便不会如此受人关注。目前,几乎所有的主流开发平台上都有了Actor模型的实现,如Java平台下的Jetlang以及.NET平台下的MS CCR和Retlang;还有一些Actor框架专为特定语言设计,如F#的MailboxProcessor以及Scala的Actor类库;甚至微软还基于MS CCR构建了一门新的语言Axum。

不过对于.NET平台下的开发人员来说,我们最常用的语言是C#。无论您是在使用MS CCR还是Retlang(亦或是我写的ActorLite),在消息的执行阶段总是略显尴尬。本文的目的便是提出一种适合C# Actor的消息执行方式,而这种执行方式还会成为我以后公开的C#中“模式匹配”的基础。

Erlang中的执行方式

本文将分为三个部分,您目前正在阅读的***部分,将会观察Erlang是如何执行消息的。有对比才会有差距,也正是由于Erlang在Actor模型上的示范作用,我们才会意识到C# Actor在使用上有多么的不方便。

作为示例,我们还是使用最经典的乒乓测试。乒乓测试的效果很简单:ping和pong为两个Actor对象,首先由ping向pong发送一个“Ping”消息,pong在接受到Ping消息之后,将会向ping发送一个“Pong”消息。在双方“乒来乓去”几个回合后,ping将会向pong发起“Finished”,从而停止交互。

乒乓测试的Erlang的实现代码如下:

  1. -module(tut15).  
  2.  
  3. -export([start/0, ping/2, pong/0]).  
  4.  
  5. ping(0, Pong_PID) ->  
  6.     Pong_PID ! finished,  
  7.     io:format("ping finished~n", []);  
  8.  
  9. ping(N, Pong_PID) ->  
  10.     Pong_PID ! {ping, self()},  
  11.     receive  
  12.         pong ->  
  13.             io:format("Ping received pong~n", [])  
  14.     end,  
  15.     ping(N - 1, Pong_PID).  
  16.  
  17. pong() ->  
  18.     receive  
  19.         finished ->  
  20.             io:format("Pong finished~n", []);  
  21.         {ping, Ping_PID} ->  
  22.             io:format("Pong received ping~n", []),  
  23.             Ping_PID ! pong,  
  24.             pong()  
  25.     end.  
  26.  
  27. start() ->  
  28.     Pong_PID = spawn(tut15, pong, []),  
  29.     spawn(tut15, ping, [3, Pong_PID]).  

由于Erlang的函数式编程,尾递归,receive原语等特殊的语言特性,其乒乓测试的实现或语义上可能和其他语言有一定区别。不过我们现在还是关注Erlang在消息执行时的特性:模式匹配。

虽然Erlang有诸多优秀特性,但是它的数据抽象能力非常有限。在Erlang中常用的数据结构只有三种:

原子(atom):原子使用小写开头的标识符来表示。您可以把原子认为是一种字符串常量来看待,事实上它除了作为标识之外也没有额外的作用。

绑定(binding):大写开头的标示符则为绑定,您可以近似地将其理解为“只能设置一次”的变量。一个绑定内部可以保存任何数据,如一个进程(Erlang的概念,并非指操作系统进程)的id,一个数字,或一个字符串。

元组(tuple):顾名思义,“元组”即为“单元的组合”,单元即为“原子”,“绑定”以及其他“元组”,通过某种方式结合起来。如上述代码中{ping, Ping_PID}便是一个由原子“ping”和绑定“Ping_PID”组成。当然您也可以写成{do, {ping, Hello, World}, 7}这种嵌套的元组结构。

Erlang中的receive原语的作用是接受下一条消息,直到有可用消息时它才会执行下面的代码。Erlang使用了模式匹配(Pattern Matching)来表现接受到不同消息时的逻辑分支。如pong的实现:

  1. pong() ->  
  2.     receive  
  3.         finished ->  
  4.             io:format("Pong finished~n", []);  
  5.         {ping, Ping_PID} ->  
  6.             io:format("Pong received ping~n", []),  
  7.             Ping_PID ! pong,  
  8.             pong()  
  9.     end. 

在这段代码中,receive将会设法将消息与两种模式进行匹配:

原子finished,表示测试结束。

元组{ping, Ping_PID},表示一个元组,其中有两个单元,首先是ping原子,其次是Ping_PID绑定。

在成功匹配了某个模式之后,其中的绑定也会随之被赋上特定的值。如匹配了{ping, Ping_PID}之后,Ping_PID便被赋值为ping这个Actor对象的标识符。而在接下来的逻辑中,便可以使用这些“绑定”中的值。由于元组的结构不会受到任何限制,因此开发人员可以使用它来表示任意的抽象数据类型——更确切地说,应该是“数据结构”吧。

Erlang的优势与缺陷

Erlang在消息执行方式上的优势在于灵活。Erlang是弱类型语言,在实现的时候可以任意调整消息的内容,或是模式的要求。在Erlang进行模式匹配时往往有种约定:使用“原子”来表示“做什么”,而使用“绑定”来获取操作所需要的“数据”,这种方式避免了冗余的cast和赋值,在使用的时候颇为灵活。然而,世上没有***的事物,Erlang的消息执行方式也有缺陷,而且是较为明显的缺陷。

首先,Erlang的数据抽象能力实在太弱。如果编写一个略显复杂的应用程序,您会发现程序里充斥着复杂的元组。您可能会疲于应对那些拥有7、8个单元(甚至跟多)的元组,一个一个数过来到底某个绑定匹配的是第几项,它的含义究竟是什么——一旦搞错,程序便会出错,而且想要调试都较为困难。因此,也有人戏称Erlang是一门“天生会损害人视力的语言”(令人惊讶的是,那篇文章居然搜不到了,我们只能从搜索引擎上看出点痕迹了)。

而我认为,这并不是Erlang语言中***的问题,Erlang中***的问题也是其“弱类型”特性。例如,现在有一个公用的Service Locator服务,任意类型的Actor都会像SL发送一个消息用于请求某个Service的位置,SL会在得到请求之后,向请求方发送一条消息表示应答。试想,如果SL的功能需要有所修改,作为回复的消息结构产生了变化,那么我们势必要修改每一个请求方中所匹配的模式。由于消息的发送方和接受方在实际上完全分离,没有基于任何协议,因此静态检查几乎无从做起。一旦遇到这种需要大规模的修改的情况,Erlang程序便很容易产生差错。因为一旦有所遗漏,系统便无法正常执行下去了。

您对Erlang的感觉如何?这是一门会影响您编程思维的语言。老赵建议,即使您平时不会使用Erlang,也不妨简单接触一下这门语言。它的并发或容灾等特性给了我许多启示。相信您会有不少收获。

【编辑推荐】

  1. Erlang面向分布与并发的编程语言
  2. Erlang十分钟快速入门
  3. 因并发而生 因云计算而热:Erlang专家访谈实录
  4. 浅析Erlang分布的核心技术
  5. 开源Erlang真的能成为下一代Java语言吗?
责任编辑:yangsai 来源: 老赵点滴
相关推荐

2009-08-04 17:34:27

Erlang的Acto

2022-03-26 09:06:40

ActorCSP模型

2023-05-07 07:56:53

Python方式

2022-11-03 15:22:15

数据结构Python

2009-08-04 17:52:25

ActorLite强类型

2009-08-05 16:04:27

C# Actor模型

2021-07-30 13:35:43

共享内存 Actor

2012-07-18 11:31:02

ibmdw

2012-11-15 10:18:11

IBMdw

2010-05-24 18:15:34

SVN中Branch和

2013-04-07 10:01:26

Java异常处理

2023-08-10 08:01:36

RDB数据AOF

2023-01-05 08:27:04

Stream执行流程

2009-07-30 10:59:44

Scala和Erlan多核

2020-09-23 22:36:27

分布式架构系统

2009-08-04 17:27:18

Actor模型

2017-01-15 21:20:30

SparkApache Spar数据科学

2011-04-28 11:07:01

灵敏率音箱

2021-11-12 14:11:21

深度学习编程人工智能

2011-08-29 10:35:53

反射方式C#
点赞
收藏

51CTO技术栈公众号