Hello World
Spiga

适合C# Actor的消息执行方式(1):Erlang中的模式匹配

2009-07-09 00:18 by 老赵, 8729 visits

前言

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

Erlang基于Actor模型实现,我们甚至可以这样认为,没有Erlang在业界竖立的丰碑,Actor模型便不会如此受人关注。目前,几乎所有的主流开发平台上都有了Actor模型的实现,如Java平台下的Jetlang以及.NET平台下的MS CCRRetlang;还有一些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的实现代码如下:

-module(tut15).

-export([start/0, ping/2, pong/0]).

ping(0, Pong_PID) ->
    Pong_PID ! finished,
    io:format("ping finished~n", []);

ping(N, Pong_PID) ->
    Pong_PID ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_PID).

pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

start() ->
    Pong_PID = spawn(tut15, pong, []),
    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的实现:

pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

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

  1. 原子finished,表示测试结束。
  2. 元组{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,也不妨简单接触一下这门语言。它的并发或容灾等特性给了我许多启示。相信您会有不少收获。

相关文章

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

30 条回复

  1. James.Ying
    *.*.*.*
    链接

    James.Ying 2009-07-09 00:23:00

    哈哈,沙发一下,明天看

  2. 老赵
    admin
    链接

    老赵 2009-07-09 00:27:00

    @James.Ying
    新婚快乐!
    // 不过你的文章为啥没法发表评论啊。

  3. pandaren
    *.*.*.*
    链接

    pandaren 2009-07-09 00:46:00

    强力插入 ...

    友情提示
    老赵 该换照片了

  4. 老赵
    admin
    链接

    老赵 2009-07-09 01:06:00

    @pandaren
    换照片?为什么?

  5. xiaotie
    *.*.*.*
    链接

    xiaotie 2009-07-09 01:12:00

    @James.Ying
    你那篇回不上,在这边祝福一下。。。。。。

  6. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-07-09 06:17:00

    up

  7. James.Ying
    *.*.*.*
    链接

    James.Ying 2009-07-09 06:48:00

    Jeffrey Zhao:
    @James.Ying
    新婚快乐!
    // 不过你的文章为啥没法发表评论啊。


    jquery冲突了,郁闷

  8. Kevin Zou
    *.*.*.*
    链接

    Kevin Zou 2009-07-09 08:16:00

    不懂,有空學習一下

  9. 老赵
    admin
    链接

    老赵 2009-07-09 09:00:00

    @Kevin Zou
    不如现在就开始吧

  10. 妖居
    *.*.*.*
    链接

    妖居 2009-07-09 09:26:00

    “相关文章”是骗人的!!!害得我猛点了一阵,才发现原来都是同样的链接…… - -!!!

  11. 老赵
    admin
    链接

    老赵 2009-07-09 09:31:00

    @妖居
    预留链接,呵呵

  12. 亚历山大同志
    *.*.*.*
    链接

    亚历山大同志 2009-07-09 09:35:00

    老赵也在研究Erlang啊,哈哈,共同进步

  13. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-07-09 09:50:00

    终于来了。

  14. gjcn
    *.*.*.*
    链接

    gjcn 2009-07-09 11:23:00

    激情无极限啊。又在看erlang。

  15. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-07-09 12:17:00

    这个话题插不上话 不好玩

  16. 老赵
    admin
    链接

    老赵 2009-07-09 13:53:00

    @韦恩卑鄙
    看看书就好玩了嘛

  17. 老赵
    admin
    链接

    老赵 2009-07-09 13:54:00

    @亚历山大同志
    Erlang like programming,oh yeah

  18. IE8用户[未注册用户]
    *.*.*.*
    链接

    IE8用户[未注册用户] 2009-07-09 19:52:00

    if ($.browser.msie && $.browser.version < 7) {
    eval("ale" + "rt('IE 6? Please use IE 8 instead!')");
    window.location = "http://www.microsoft.com/windows/internet-explorer/";
    }

    为什么不支持IE8呢?

  19. 老赵
    admin
    链接

    老赵 2009-07-09 21:44:00

    @IE8用户
    真奇怪,我的代码没有写错啊。
    我把这两行注释了,一会儿研究一下,谢谢提醒。

  20. 老赵
    admin
    链接

    老赵 2009-07-09 22:35:00

    @IE8用户
    已经改成了这个样子,应该没有问题了吧:

    <!--[if lt IE 6]>
    <script language="javascript" type="text/javascript">
    eval("ale" + "rt('IE 6? Please use IE 8 instead!')");
    window.location = "http://www.microsoft.com/windows/internet-explorer/";
    </script>
    <![endif]-->
    

  21. Leo Zou
    *.*.*.*
    链接

    Leo Zou 2009-07-09 22:41:00

    "试想,如果SL的功能需要有所修改,作为回复的消息结构产生了变化,那么我们势必要修改每一个请求方中所匹配的模式。"
    ----------
    我认为,这可能正是Erlang的灵活性所在。至于SL服务的消费者,他们应该提供有自己出错的处理和必要提醒,况且Erlang的自动测试是很容易的。如果使用静态类型检查,在接口都变了的情况下,那也免不了要修改每一个消费者,另外这样以来可能也丧失了Erlang在线修改代码的优点。
    另外,Erlang的元组表现方式确实有待改进。赞同楼主的观点。
    Erlang我了解不多,以上是我的愚见。期待楼主的下一篇文章!

  22. 老赵
    admin
    链接

    老赵 2009-07-09 23:11:00

    @Leo Zou
    你的说法的确有一定道理,我没有想到,谢谢指出!
    不过我忽然想到,其实静态检查并非将消息发送方和接受方耦合起来了……关于这点我需要再想想。:)

  23. jackyz.zhao[未注册用户]
    *.*.*.*
    链接

    jackyz.zhao[未注册用户] 2009-07-10 15:44:00


    cnblogs 不支持 trackback ?

    针对这两个问题,回了一篇,请见:http://erlang-china.org/study/puzzle-in-erlang_pattern_match.html

  24. 老赵
    admin
    链接

    老赵 2009-07-10 16:18:00

    @jackyz.zhao
    谢谢,我回复了。
    我对Erlang的理解仅限于资料,还有一些小程序,没有写过任何应用。
    有什么问题请多多指出。:)

  25. 老赵
    admin
    链接

    老赵 2009-07-10 22:52:00

    一整天极少评论啊,不知道是不是不能评。
    如果大家发现什么问题,请及时Email和我联系,或者在space.cnblogs.com中短消息给我。

  26. 为了梦想[未注册用户]
    *.*.*.*
    链接

    为了梦想[未注册用户] 2009-07-11 23:13:00

    估计是都没接触这块而无话可说

  27. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-07-13 11:05:00

    Jeffrey Zhao:
    一整天极少评论啊,不知道是不是不能评。
    如果大家发现什么问题,请及时Email和我联系,或者在space.cnblogs.com中短消息给我。


    能评论,只是...无话可说,知识面没你这么广。

  28. new123456[未注册用户]
    *.*.*.*
    链接

    new123456[未注册用户] 2009-07-15 16:44:00

    <一种适合C# Actor的消息执行方式(下):较好的解决方案>为啥连接不过去呢

    最近对基于消息,FP的应用程序设计最近比较感兴趣。
    等楼主出C#模拟erlang的完美解决方案。



    mvc总感觉不自然。
    从FP角度看,一般应用程序无非是DB处理FP,表示画面和UI事件处理的FP,业务运算用的FP,画面迁移控制FP


    1.把画面的表示,画面迁移控制,业务运算,DB访问都设计为类似erlang的独立线程。
    2.程序的控制流表现为各个模块线程之间进行消息发送。
    3.用DTO传来传去的东西直接放到类似erlang的ETS中。

    这样的模块划分是不是更容易理解一些。



  29. MM2009[未注册用户]
    *.*.*.*
    链接

    MM2009[未注册用户] 2009-07-22 22:42:00

    请问下elrang用什么编辑器好啊?

  30. 老赵
    admin
    链接

    老赵 2009-07-29 19:10:00

    @new123456
    fp不是erlang的特点,只是fp的no side effect很适合并行,所以就这么设计了啊。
    其实erlang设计成其他的方式,只要no side effect,感觉都是可以的。
    这也是其他actor模型的做法。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我