2010年11月2日星期二

Erlang笔记

需要进一步整理。
Erlang主要分为两部分,首先简要总结语言形式的特点和代码写作风格,然后总结配置Erlang时的种种问题。

Log #2

0001
我又搞错了,Mathematica中的东西在Erlang里面基本无法实现。毕竟两者基本的设计目的就有很大差别。今天重点看过了 Programming Erlang的第八、九、十章,然后草草地翻阅到了第十六章。八九十章着重介绍如何创建和消灭进程,以及进程之间的通信。从这里开始Erlang的神奇之处就逐渐显示出来了。一个Erlang进程,包含了如下信息:

1)进程标识
进程标识是在进程创造出来的时候分配的,这个标识是由创造进程的spawn表达式返回的值,这个值必须同一个变量相关联,否则就会被当做垃圾回收掉。

2)它会接收怎样的信息
之前所写的Erlang代码,都是我们从控制台发送的消息。当这段程序执行完毕之后,执行(对应英文是"evaluate"而不是"run")的结果返回到控制台。但是如果需要这个结果的不是我们而是另一个进程呢?于是进程之间就需要以某种方式传递信息。虽然说通信是接收消息和发出消息共同构成的,但这里只说“接收”而非“发出”,原因在于一个进程必须接收消息才能执行,否则就会被Erlang虚拟机当做垃圾回收掉。但是接收到信息之后,进程可以只进行运算而不发出消息,当然也可以发出信息,也有可能立刻死掉(如果这个消息就是要终止这个进程的话)。

更具体地说,一个进程其实就是一个应激规则库,库中每一个规则包含一个模式和对应的行为。这个模式用来匹配它所接受到的消息。由于消息本身是用Erlang的数据结构表示的,故此处的模式就是匹配这些数据结构的模式。具体表示如下:

receive
MessagePattern1 [when is_Predicate()] ->Action1;
MessagePattern2 [when BooleanExpression] ->Action2;
...
end

其中MessagePattern i 就是用来匹配消息的模式。when是一个关键字,when后面是求值后能够返回一个真值的表达式。它可以是Erlang中的谓词函数,也可以是布尔表达式。模式匹配的结果要保证when后的表达式值为真。

这其实就是人工智能中所提到的agent,它是由一系列的if-then构成的应激机制。然而agent只是一种概念,因为if-then中的if有可能是通过类似Erlang的消息机制传递进来,也可能需要通过agent本身去学习。

3)生存周期
生存周期是有两种方式定义的。一个是作为receive的一个备选项,一个是将receive置入一个递归函数中。作为receive的备选项是 after,它提供了当进程因为某种原因在可接受的时间内没有接收到任何消息时采取的措施。erlang内部设置了进程接收消息的一个时间,在这个时间内如果此进程没有接收到消息进而产生任何动作,那么这个进程就会被挂起,虚拟机会去响应其它进程,直到这个进程收到消息为止。有了after,进程就会在超时之后产生一个和接收到消息一样的动作,然后被终止掉,等着garbage collector来收尸体。

另外一种情形涉及到进程的常用写法:

loop()->

    receive
    MessagePattern1 [when is_Predicate()] ->Action1;  loop();
    MessagePattern2 [when BooleanExpression] ->Action2;  loop();
    ...
    end

这就意味着,这个进程的应激机制被放进了一个无限递归的无参数无返回值的函数中。当它执行完某个action之后不会消失,而是一直留在内存里等待它的戈多。需要注意的是,这里的loop是一个尾递归。书中对尾递归做了很简单但是相当精辟的陈述:尾递归就是当前一次迭代产生的表达式经过求值之后而非压栈之后才进入下一次迭代,如果这里是线性递归,由于没有终点因此当前的表达式会一直压栈而不会执行。我以后会写尾递归专题文章,这里就不赘述了。

4)死亡信息
就像人一样,有的人离开这个世界之前会大声呼救,有的人离开这个世界时就像我爸一样静悄悄。Erlang进程在被回收之前也会有类似的呼叫行为,它会产生一个信息向所有其他的进程广播,不过当然呼叫不呼叫全看这个进程怎么定义,其他程序也可以定义听见或者听不见。以下情况需要特别讨论:

a.誓死盟约link()
在一个进程Prog1中可以通过link(Prog2)将两个进程建立这种“誓死盟约”。如果Prog1是正常死亡,比如是完成任务后通过返回一个 normal信息或者执行exit()“寿终正寝”,那么Prog2会收到这个信息但不采取任何行动(当然或许听到这个消息会有点伤心)。但是如果 Prog1产生了一个错误比如除零,或者产生了其它任何一种不是正常终止的消息,那么Prog2也会跟着自杀。

b.独当一面的process_flag(trap_exit, true)
一个进程可以利用process_flag来把自己改造成强健的“异常退出捕获”进程,或者叫做系统进程。系统进程承担起汇报同在誓死盟约中的其他进程的死亡信息,因此首先一般的死亡讯息不会令系统进程自杀,而且当系统进程捕获到一个死亡信息之后,便不会再把此信息广播给其他进程。

c.鬼来电kill
kill是一个标识而不是一个操作,如果一个进程返回的是kill,那么誓死盟约内的所有进程(包括系统进程)都会自杀。


在这里需要补充几点:

1.消息的收发是异步的
这一点决定了消息之于进程并不相当于参数之于函数。消息不是进程的参数,因为如果是这样的话,在进程正在执行某个消息对应的动作时会忽略掉这个时候收到的其他消息。这就好像我坐火车跑到上海去找你聊天,结果你却跑到杭州玩去了。而在真实世界的容错系统中,我到上海找你而你恰好在的情况更不常见。消息并不是直接发送到进程中去,而是留在了进程的信箱中(一个队列)。receive其实是在一封一封地读收件箱里面的信,然后按照信中说的去执行对应的行为。异步确保了系统的容错性,这是并行系统的精髓。

2.进程是可以注册的
这个其实就是把进程标识和一个原子相关联。但这其实没什么用,因为进程标识本身就是变量的值,那个变量就应当说明这是一个什么进程,不需要多此一举了。

好,就语言上来说,基于一个Erlang虚拟机的并发系统就没有问题了。

0010
接下来就是虚拟机本身设置的问题,目前的多进程仅能在一个虚拟机的支持下实现,如果想要在不同计算机上的两台虚拟机上实现,就需要配置虚拟机的 TCP/UDP端口。这些我还没有仔细看,因为关于网络协议我还有很多东西不懂。但是看到这里我突然意识到一个严重的问题,我之前对 Mathematica的了解也仅限于Mathematica语言本身,但是关于MathKernel,也就是Mathematica虚拟机的使用方法却几乎一无所知。大概在十年前,Roman Maeder就已经弄出了不同MathKernel通信的解决方案。事实情况是,仅知道抽象语言,而不知道虚拟机的工作方式,或者至少是虚拟机的使用方法,是没有办法真正实现产品的。然而Mathematica关于这方面很少有公布出来的文档,这很让人郁闷。

0011
热代码替换。这是一项freakin到家的技术,并且几乎只有Erlang能轻松实现。热代码替换指的是服务端脚本运行时将它进行修改,服务端程序会把收到的包含替换代码的消息直接纳入到自己的身体里!这就是说,服务器在进行维护的时候不需要停机,像做解剖手术一样重新把服务器端代码编译一遍,而是在设计的时候就为它创造一个消化系统,使得它可以一边工作一边汲取营养。为什么IBM等大公司没有应用这个技术?这不是因为前瞻性,而是因为传统。因为爱立信是做交换机起家的,尽管不是计算机公司,但是它却比所有的计算机公司都先接触并熟稔于并行架构。抱着Java不撒手的IBM现在傻眼了。此外,erlang 也有动态执行功能,它可以生成一个文本,然后再将这个文本按照erlang代码执行,和Python的exec()是一样的。昨天木鱼提到的进化代码,现在已经具有实现的基础了。如果服务器能够在互联网上搜索代码和信息并补充道其本身的代码中,挖哈哈哈,那可就太恐怖了。

0100
好,那么明天开始补充一下网络知识。然后去找关于MathKernel的内容。如果了解MathKernel间的通信方式,我知道怎么设计基于MathKernel的热代码替换。另外继续看其他库的源码。

0101
欧了。

-EOF-

Log #3

今天事情还没做完按说是不应该上来写日志的,不过我注意到Wolfram耍了一个很奸诈的诡计,就是混淆了“并行计算”和“分布式计算”的区别。

并行计算和分布式计算的共同点是它们都需要很多计算节点来分别完成一个大问题中的各个部分。这些计算节点可能在局域网里不同的机子上,也可以在同一个机子里不同的CPU核上,也可以在同一个CPU核里不同的进程中,总之计算节点是一个抽象的概念。

那么并行计算和分布式计算有什么区别呢?并行计算指的是所有计算节点彼此相互连接,存在传递消息的通路,而分布式计算中的计算节点是存在阶级矛盾的,其中主节点用来将任务拆分成子任务,然后发送给从节点。从节点解决完这个问题之后再去从主节点那里领取一个新的任务,但是从节点之间却并无通信。

M@7 中的ParallelEvaluate更确切地应该叫做DistributedEvaluate,它把Kernel间的runtime通信封装在了 ParallelEvaluate这个函数中,对于用户来说消息传递的过程是不可见的。在M@2.2中事实上还保留了不同Kernel间应用TCP协议进行通信(那个时候还没有多核CPU),在新闻组中翻到了一篇写于1996年的文章,提到了KernelLink的用法,但是在Wolfram官方网站上,这部分文档已经被删除了。

现在的问题在于,如果想应用M@Kernel来实现类似Erlang的并行编程(注意M@中的表达式都是顺序执行的,同一个Kernel所执行的任务之间是没有通信的,因此它不能叫做“并发编程”(concurrency)而只能叫做“并行”),而Kernel又是独立的操作系统进程,Kernel之间的通信就只能通过MathLink来实现。因为M@Kernel无法向控制台返回一个二进制流然后再被调用它的程序捕获,并且即使可以这样也会很慢。

接下来要去看MathLink的文档,当然Programming Erlang昨天没有仔细看的地方今天要看完。

欧了。


P.S.
经济基础决定上层建筑,而经济基础是由具有消费能力的人的需求决定的,而具有消费能力的人的需求是由技术创造,也是由技术满足的。因此,上层建筑高度依赖科技。尽管续大师从之前非常嚣张地同我天朝对抗,到现在开始一边谄媚校内管理员一边进行无力的暗讽,但事实上这些话都是球用也不顶。想要改变什么,你必须要去做些什么,甚至可以说做任何事情都可以,但是必须去做。金胖子向全世界送出导弹,要比向全世界送出主体思想更有说服力。我们现在的整个局面,其实都是由技术基础决定的。大到花40M弄出来的屏蔽软件,小到四六级考试时黄豆大的无线耳机,什么法律,什么规则,全都轻而易举地颠覆了。以后随着技术的发展,无论是政治还是人伦,一切都会不断变化。

我们无从知晓历史,因为记忆是不可靠的。我们也无从了解未来,因为我们无法了解现在。这个世界在走向混沌,我们首先需要关注的自己的生存。你我不必就整个时代发表评论,因为这个时代本身就是由你我的饮食起居影响着。

-module  (server).
-export ([start/2, rpc/2]).
%%Name of the module is "server", "start" and "rpc" are the functions can be call externally.

start(Name, Mod)->
  register(Name, spawn(fun ()->loop(Name, Mod, Mod:init()) end )).
%%Here the Mod is the name of the module which communicates with the new created process. Name is the name of the new created process.

rpc(Name, Request)->
Name ! (self(),Request),
receive
  {Name, Response}-> Response
end .
%%RPC is the remote processing control. the framework is defined in the server module, but the messages which to be sent via RPC are defined in the module below. RPC sends the Request to the process with name Name. self() indicates the process sending message via RPC. the Response sent back from the process with name Name will be passed to the process which uses RPC.

loop(Name, Mod, State)->
receive
  {From, Request}->
    {Response, NewState}=Mod:handle(Request, State),
    From ! {Name, Response},
    loop(Name, Mod, NewState)
end .
%%loop is the main body of the new created process. When it received the message via RPC, it will start an action defined in the module below. After preformed the expected action, it will send the Response back to the process, which indicated by From.

This is the core of a server, the module neither defines any acceptable information from client, nor specifies how it behaves. It only create a process with requested name, and initiate it as requested. It also defined a communication framework, so that the client can send message via the framework to access the new created process. Finally, it defines the content of the new created process, which indicates that once the message received, it will act as requested, send a response to client process, and then stand by for next message.In a word, the server is only an executive, which has no prior information about what to hear and what to execute. Just like a main() in C.

-module (name_server).
-export ([init/0, add/2, whereis/1, handle/2]).
-import (server,[rpc/2]).
%%it inherits the definition of RPC from the server module, and has 4 function can be called externally.

%%Client routines
add(Name, Place)->rpc(name_server, {add, Name, Place}).
whereis(Name)->rpc(name_server, {whereis, Name}).
%%Defines the message corresponding to the action to be sent.

%%call back routines
init() -> dict:new().
handle({add, Name, Place}, Dict)->{ok, dict:store(Name, Place, Dict)};
handle({whereis, Name}, Dict)->{dict:find(Name, Dict), Dict}.
%%Defines how the server process behaves.

Client provides not only instructions, but also the introductions to those instructions.



Programming Erlang那本书里面举了一个看起来简单到必须得一次成功的分布式计算的例子,但是我却为这个例子花了将近三天时间。原因是这本书首先假定你处于一个理想情况并且能够顺利地把它实现,之后才零零散散地想到如果你哪里会有问题该怎么解决。当然正确的状态只可能有一个,但是错误的情况却可能层出不穷,解决问题需要一个社区的努力。

但是通常情况下,如果你不知道这个问题的答案,通常是因为问题出在更低一级的层次,并且你甚至不知道那里出了问题。这和写程序不一样,写程序是想好之后才动手,但是解决配置问题只有你动手之后才能开始想。然而从给定的条件下找原因并最终解决问题,这样的事情我很少做,以后需要多经历这样的事情。

1)
书上的例子使用了一个rpc:call/4。正确的情况如书中所示,在认真抄书没有拼写错误,以及没有忘记先输入kvs:start()的情况下,你可能会收到一个错误应答叫做{badrpc, nodedown}。它是说节点没有连接到位。那么你你需要使用net_adm:ping(node@Hostname)试验一下。根据你之前得到的结果,ping的结果很可能是pang。注意pang是失败的回应,成功的回应是pong。

2)
如果你在得到pang回应的时候有一段时间console不响应输入,而另外一台机子的console上面没有任何反应,这说明发出ping请求的节点就根本没有找到另外那个节点所在的机子。所以,你需要在操作系统的shell下再去ping那台机子。如果操作系统下就没有ping成功,那可能需要考虑网络设置,比如防火墙是不是把ping包拦下来了。[我曾在这里花了一上午才发现是瑞星干的]

3)
注意还有一种可能,就是ping IP地址成功了但是ping机器名称没成功,那是因为机器名和IP地址没有关联。关联的方法是编辑机内的hosts文件。windows平台这个文件在system32/drivers/etc里,而*nix在/etc里。把机子的IP和hostname关联起来,在操作系统shell去ping,成功后再到Erlang下面去ping。

4)
注意,即使这样仍然没成功,可能出现的情形是仍然返回pang,但是速度比之前快了很多,并且被ping的节点提示一个error report,说外来的访问被禁止了。很好,这下问题就明朗了,剩下的问题是cookie。cookie的内容是一个atom,算是不同nodes之间通信的一个暗号。只有拥有相同暗号的nodes之间才能进行通信。cookie的设置可以在erl的参数中设置,也可以进入erlang的console之后用erlang:set_cookie(node(), cookie)进行设置,并用erlang:get_cookie()进行查询。

5)
即使这样仍然可能不成功,也许是因为erlang的环境没有设置正确。在linux下安装erlang包后环境变量就设置好了,但是在windows中却需要自己重新设置。erlang可以通过code:add_patha(z)添加查找module存放的目录,但需要小心的是它们是已经编译过的beam文件而不是erl源代码。erl文件erlang只能在操作系统调用erlang的当前目录下进行查找,而不会到那些预设的目录中查找erl文件。

如果你使用的是eclipse+Erlide是不会遇到这些问题的,你不需要手工把erl编译成beam,IDE都帮你做好了。

好了,infrastructure到现在为止就算建立成了,今天晚上喝瓶啤酒庆祝一下。 : )

没有评论:

发表评论