Erlang02_并发编程(1)
总篇:7
编辑于 2025/4/30 20:00
截稿于 2025/4/30 21:24
基础语法与编程实战后,进入并发编程章节。据书中所言,erlang并发基于 “进程”而非 线程, 而此 进程又不等于引用进程。
原语
像case、when、if 那样,并发有对应的原语:
- Pid = spawn(Fun)
spawn:开启一个新的进程执行匿名Fun函数,返回Pid就是该进程的id,可以向其发送消息
通常这个函数命名为loop,即:
spawn(fun()->loop() end)
也可以写成传统的MFA:
spawn(server,loop,[])
- Pid!Message ::any()
向Pid进程发送Message消息,该消息类型任意,进程函数可以接受该消息执行对应逻辑。
操作结束原样返回Message.
- 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中匹配成功后移出邮箱。
示例
- 编写一个二元计算器进程模块,
-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取别名),原语:
- true / error = register(Atom,Pid)
resiter(server,ServerPid), 将SesrverPid进程注册为server名的进程,成功注册返回true,其他返回错误信息
客户端可以直接向server发送消息。

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

以上属于系统进程。
简易聊天室实战
聊天室是所有语言并发编程中不得不品的一环,现在已经有了server,client示例,将其改造为以下需求:
-
一个服务器进程,三个客户端进程,
-
注册这四个进程,命名为chat_server,chater1,chater2,chater3,
-
server能接收:{From,ToList,Msg} -> 向ToList发送Msg,{From,Msg} -> 向chater1,chat2,chat3 发送{From,Msg}.
-
创建聊天服务器:
-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.
- 创建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)的形式,合并这三个客户端,在创建进程时赋予不同的名称。
以上 并发编程的基础入门部分。
浙公网安备 33010602011771号