Erlang02_并发编程(1)

总篇:7

编辑于 2025/4/30 20:00

截稿于 2025/4/30 21:24

基础语法与编程实战后,进入并发编程章节。据书中所言,erlang并发基于 “进程”而非 线程, 而此 进程又不等于引用进程。

原语

像case、when、if 那样,并发有对应的原语:

  1. Pid = spawn(Fun)
spawn:开启一个新的进程执行匿名Fun函数,返回Pid就是该进程的id,可以向其发送消息
通常这个函数命名为loop,即:
  spawn(fun()->loop() end)
也可以写成传统的MFA:
  spawn(server,loop,[])
  1. Pid!Message ::any()
向Pid进程发送Message消息,该消息类型任意,进程函数可以接受该消息执行对应逻辑。
操作结束原样返回Message.
  1. receive ... end
recive
  pattern1 when true/false->
    expression1;
  pattern2 when true/false->
    expression2;
  [after Time] ->
    expressionfinal
end.
1.服务器收到消息后,根据模式匹配到哪一个pattern执行对应expression,阻塞式,因此:
2.after Time: 超时机制,当服务器收到消息而以上所有模式没有匹配到,超过Time毫秒后将执行保底逻辑,
消耗掉这条消息,将其从邮箱中移除。
该项为可选项,不写表示一旦有无法匹配的消息进入将永久阻塞代码块。
Time =0时,表示立即超时。
3.邮箱:每个进程都有一个消息邮箱,会将收到的消息先存入邮箱,在recive中匹配成功后移出邮箱。

示例

  1. 编写一个二元计算器进程模块,
-module(server).
-export([start/0, loop/0]).

start() ->
    spawn(fun() -> loop() end).

loop() ->
    receive
        {From, Num1, Num2, Operator} when
            is_number(Num1) and is_number(Num2) and is_atom(Operator)
        ->
            Result =
                case Operator of
                    '+' -> Num1 + Num2;
                    '-' -> Num1 - Num2;
                    '*' -> Num1 * Num2;
                    '/' -> Num1 / Num2;
                    _ -> invalid_operator
                end,
            From ! Result,
            loop()
    end.

得到一个进程PId, 向其发送{self(),1,2,'+'} (self()表示本进程Pid,当前为shell本身):

是原样返回Message了,因为没有函数去接收结果。想要得到结果,就要采用C/S模式,由客户端向服务端发送消息,得到Result后输出(书上有C/S在同一文件示例)。 现在新建一个客户端文件:

-module(client).
-export([send_request/2]).

send_request(Server, {Num1, Num2, Operator}) ->
    Server ! {self(), Num1, Num2, Operator},
    receive
        Result -> Result
    end.

客户端仍然是本shell,但是由函数体内向服务器发送消息然后阻塞 receive ...end 匹配消息执行Result ->Result 逻辑。如下图:

画板

也就是说,客户端知道服务器的Pid,就可以向其发送消息,接收结果。

注册进程

以上server:start() 创建了一个进程返回其Pid,客户端得到其Pid就可以向服务端发送消息,但是Pid:<0.89.0> 不方便记忆,对此,我们可以注册进程(就是对进程PId取别名),原语:

  1. true / error = register(Atom,Pid)
resiter(server,ServerPid), 将SesrverPid进程注册为server名的进程,成功注册返回true,其他返回错误信息
客户端可以直接向server发送消息。

  1. unregister(Atom)
unregister(server),注销这个名称的进程,此后客户端无法通过server向其发送消息,只能用其Pid
  1. whereis(Atom)
Pid | undifined = whereis(server),检查是否有名叫server的进程
  1. registered()
registered(),返回本erlang环境中所有已注册进程

以上属于系统进程

简易聊天室实战

聊天室是所有语言并发编程中不得不品的一环,现在已经有了server,client示例,将其改造为以下需求:

  1. 一个服务器进程,三个客户端进程,

  2. 注册这四个进程,命名为chat_server,chater1,chater2,chater3,

  3. server能接收:{From,ToList,Msg} -> 向ToList发送Msg,{From,Msg} -> 向chater1,chat2,chat3 发送{From,Msg}.

  4. 创建聊天服务器:

-module(chat_server).
-export([start/0, loop/0]).

start() ->
    Pid = spawn(fun() -> loop() end),
    register(chat_server, Pid).

loop() ->
    receive
        {_From, Message} ->
            chater1 ! {self(), Message},
            chater2 ! {self(), Message},
            chater3 ! {self(), Message},
            loop()
    end.

  1. 创建chater1 2 3:
%%改数字分三个文件
-module(chater1).
-export([start/0, loop/0, send_message/2, send_message/3]).

start() ->
    Pid = spawn(fun() -> loop() end),
    register(chater1, Pid).

send_message(Message, ServerPid) ->
    ServerPid ! {self(), Message}.

send_message(Message, ServerPid, Pids) ->
    ServerPid ! {self(), Message, Pids}.

loop() ->
    receive
        {From, Message} ->
            io:format("chater1 Received message from ~p: ~p~n", [From, Message]),
            loop()
    end.

向所有人发送“hello”:

chater1向chater2发送悄悄话:

以上客户端文件是三个雷同的模块,这是不合理的,其实可以通过start(Name)的形式,合并这三个客户端,在创建进程时赋予不同的名称。

以上 并发编程的基础入门部分。

posted on 2025-04-30 21:28  依只  阅读(10)  评论(0)    收藏  举报

导航