Erlang/Elixir Erlang 入坑笔记 -- Erlang Process

yfractal · 2018年09月18日 · 最后由 yfractal 回复于 2018年09月18日 · 480 次阅读

用 Erlang Process 是 Erlang 最核心的部分。下面将用一个聊天机器人的例子来说明 Erlang Process 的运行机制。

聊天机器人比较有名的是 github 的 hubot,但 hubot 本质是输入到命令的映射,与其说是聊天机器人,不如说是指令机器人。

而聊天机器人首先要有上下文,一个主题会由多句话组成的。现实中会话也可能是混乱的,比如答非所问。最后还应该是具有较好的并发性。

Sequential programming 的方式并不容易处理,但 Erlang process 却很擅长做这样的事。

这个机器人要做什么

因为是为了说明 Erlag Process 的,所以功能尽可能简答。

这个机器人会问用户名字和年龄,之后用户可以问机器人自己的名字。

代码实现

我们直接来看代码

-module(robot).

-export([start/0]).
-export([loop/2]).

start() ->
    spawn(robot, loop, [ask_name, #{}]).

loop(ask_name, State) ->
    io:format("What's your name?\n"),

    receive
        {my_name, Name} ->
            io:format("Ok, I know your name\n"),
            NextState = maps:put(name, Name, State),
            loop(ask_age, NextState)
    end;

loop(ask_age, State) ->
    io:format("How old are you?\n"),

    receive
        {my_age, Age} ->
            io:format("Ok, I know your age.\n"),
            NextState = maps:put(age, Age, State),
            loop(answer, NextState)
    end;

loop(answer, State) ->
    io:format("You can ask me question.\n"),

    receive
        { my_age_is } ->
            Age = maps:get(age, State),
            io:format("Your age is ~p~n.", [Age]),
            loop(answer, State);
        _ ->
            loop(answer, State)
    end.

start 是程序的入口,spawn 了一个进程,开始进入循环。

1> c(robot).

2> Robot = robot:start().

What's your name?

Async & Sync

robot:start spawn 了一个进程,之后这个进程等待我们给它发消息。

我们发条消息,Robot ! {my_age_is}. Robot 没有任何返回。 这是因为, receive block 了 Robot 这个进程,同时 my_age_is 不是当前 recieve 要接收的消息。

虽然 process 处于等待状态,但我们仍能给它发消息,也就是说消息的发送是异步的。

如果我们想要同步发消息,只需要把消息传回调用的 process,然后调用的 process 使用 receive 接收返回的消息即可。

用 Erlang 做同步、异步调用还是很简单的。

mailbox

我们先看正常调用过程。

3> Robot ! {my_name, "Mike"}.
Ok, I know your name

How old are you?

4> Robot ! {my_age, 18}.
Ok, I know your age.

You can ask me question.

6> Robot ! {my_age_is}.
Your age is 18.

我们看到,这个流程是先回答名字、年龄,之后询问 robot 自己的年龄。

那如果先说年龄,再说名字呢? Erlang process 是如何处理的呢?

7> Robot2 = robot:start().
What's your name?

8> Robot2 ! {my_age, 18}.

Robot2 没任何反应,现在输入名字。

10> Robot2 ! {my_name, "Edward"}.
Ok, I know your name
How old are you?
Ok, I know your age.
You can ask me question.

我们看到机器人,收到 my_name 这条消息后,开始问我们年龄,之后告诉我们它知道我们的年龄。 这说明,机器人收到、并记录了 my_age 这条信息,当从 ask_name 这个循环,进入 ask_age 的时候,处理了 my_age 这条消息。

这是因为,每个 Erlang process 有一个 mailbox,所有消息,都会被放入 mailbox 里。 当调用 receive 的时候, receive 会从最先进入 mailbox 的消息进行处理,如果处理不了,则会尝试处理下一条, 如果都不能处理,则等待新的消息。如果可以处理,则执行对应的代码。

如果想查看 mailbox 里有多少消息,可以使用 process_info(Pid). 这条命令。

Process -- The running program

我们回过头再仔细看一下代码,会发现,代码很简单,也很有趣。

进入第一个 looprecieve 的时候,Robot 这个 process 只会处理 my_name 这条消息,

处理完这条消息后,进入下一个 loop,这个时候只会处理 my_age

这就是说,Erlang process 会等待执行一段代码,随着状态的变化,需要执行的代码也会随着变化。

这个也是和 sequential programming 最不同的一点。sequential programming 是逻辑的集合,而 concurrent programming 是运行代码的集合。

Functional Programming + Process == Object?

函数式编程最核心的想法是让状态的变化变得透明,比如 Clojure 的变量就是不可变的。但现实并非如此美好,因为现实世界是有状态的。

用 Clojure 处理有状态的问题的时候,会隐隐的蛋痛。而且 Clojure 和 Erlang 比起来也不那么纯粹,因为变量可以多次赋值,同样让状态的流转变得模糊。 再加上 hash-map 漫天飞(好吧我跑题了)。。。

回头再看面向对象,面向对象非常擅长处理状态,对象内部的状态对于外部是一个黑盒,用起来非常方便。但对象的状态对内部状态依旧是模糊的,并不容易 debug。

而 Erlang process 可以很好处的处理这两个问题。

我们把 Erlang 变一变,把 ! 改为 send,然后再换一种语法,robot.send({my_name, "Mike"}),这其实就是面向对象的方法调用啊。

process 有自己的状态,对外部是个黑盒,外部的调用都是通过消息传递(吐槽下 Python、Java,为什么有方法的同时还要有 field 这种东西,学学 ruby)。 process 自身知道如果处理这些消息。

Erlang 又是函数式的,当状态(State)变化了,就要生成一个新状态(NewState),然后再把这个新状态传到下一个 loop 里。也就是状态的流转完全可见。

debug 的时候,只要知道当前状态,传入对对应的方程,对对应的方法最调试就可以了,非常清晰。

Erlang 的 process 对外是黑盒,方便条用,对内是黑盒,方便维护和 debug。

How to learn Erlang

最后要要如何学 Erlang 呢?

elixir

共收到 2 条回复

还没看,为何后面是Elixir书呢?

chenge 回复

暗戳戳表明 Elixir 比 Erlang 更友好。对 Erlang 感兴趣,学 Elixir 才是正确的方向。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册