Erlang并发

Erlang进程间不共享内存,通信通过消息传递。进程可通过spawn创建,使用send发送异步消息,receive接收消息。错误检测通过进程链接实现,当链接的进程消亡时,其他进程会收到通知。Erlang核心是客户/服务器架构,支持超时的receive语句,且每个进程有自己的邮箱。注册进程便于通信,同时介绍了解决超时和异常退出的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

erlang进程之间没有共享内存,每一个进程都有它自己的内存,想要修改其他进程的内存,你只能向它发送一条消息。

erlang的进程不共享内存,没有锁的概念。

erlang的错误侦测机制:进程之间可以相互链接。如果一个进程消亡,那么跟这个进程有链接的进程会得到一条消息,被告诉进程已经消亡,及其原因。

erlang进程的三个原语:spawn,send,receive.

Pid = spawn(Fun):创建一个新的并发进程。对于Fun求值,返回一个Pid,可以使用Pid向进程发送消息。

Pid!Message:想标识符为Pid的进程发送消息,消息发送是异步的。!是发送消息符。它的返回值是它发送消息的本身。

receive...end:接收一个发送给当前进程的消息。
语法:

receive
Pattern1 [when Guard] ->
Expre1;
Pattern1 [when Guard] ->
Expre1;

end.

例子:

-module(process).
-export([loop/0]).
loop() ->
receive
{rectangle,Width,Ht} ->
io:format("Area of rectangle is ~p~n",[Width*Ht]),
loop();
{circle,R} ->
io:format("Area of circle is ~p~n",[3.14*R*R]),
loop();
Other ->
io:format("I don't know what the area of a ~p is ~n",[Other]),
loop()
end.

在shell中创建一个进程:

Pid = spawn(fun process:loop/0).
//Pid = spawn(fun() -> loop() end).
//Pid = spawn(process,loop,[]).
Pid ! {rectangle,5,4}. //发送消息


客户/服务器架构是erlang的核心部分。erlang的消息传递被用于客户机和服务器之间的通讯。
客户端总是通过向服务器发送请求来发起一个计算,服务器则在计算出结果之后向客户端发送一个响应。
客户机必须在请求中包含一个服务器可以响应的地址。客户自己的Pid可以用self()表示。

Pid ! {self(),{rectangle,4,5}}.


接收代码改为:

loop() ->
receive
{From,{rectangle,Width,Ht}} ->
Result = Width*Ht,
From ! Result,
loop();
{From,{circle,R}} ->
Result = 3.14*R*R,
From ! Result,
loop();
{From,Other} ->
From ! Other,
loop()
end.


完整代码:

-module(process).
-export([start/0,area/2]).
start() ->
spawn(fun loop/0).

area(Pid,What) ->
send(Pid,What).

send(Pid,Request) ->
Pid ! {self(),Request},
receive
{Pid,Response} ->
Response
end.

loop() ->
receive
{From,{rectangle,Width,Ht}} ->
Result = Width*Ht,
From ! {self(),Result},
loop();
{From,{circle,R}} ->
Result = 3.14*R*R,
From ! {self(),Result},
loop();
{From,Other} ->
From!{self(),{error,Other}},
loop()
end.


查看系统所允许的最大进程数:erlang:system_info(process_limit).
如果要超过系统允许的最大进程数,必须在启动erlang的时候带上+P参数,例如:

erl +P 500000.


带超时的receive:有时候,如果某个消息迟迟不来,receive语句可能进入无限的等待之中,为了避免这种情况,在receive语句里面加上一个超时,这个超时设定了进程接收消息时最长的等待时间。语法:

receive
Pattern1 [when Guard] ->
Expre1;
Pattern1 [when Guard] ->
Expre1;

after Time ->
Expres
end.

如果在进入receive表达式后在Time所规定的毫秒数内没有收到能够匹配的消息,那么receive就会停止等待,并对Expres求值。

只有超时的receive:可以写一个只有超时的receive语句,用它来让当前进程暂停Time毫秒。

sleep(T) ->
receive
after T->
true
end.


无限等待超时进行接收:如果receive语句的超时值被设定为原子infinity,那么系统就永远都不会触发超时。

实现一个计时器:

-module(time).
-export([start/2,cancel/1]).
start(Time,Fun) ->
spawn(fun() -> time(Time,Fun) end).

cancel(Pid) ->
Pid ! cancel.

time(Time,Fun) ->
receive cancel ->
void
after Time ->
Fun()
end.


send其实并非把一个消息传递到一个进程中去,而是把一个消息发送到进程的邮箱中去,同理,receive则试图把一条消息从那个进程邮箱中删除。
erlang中每个进程都有与之对应的邮箱,当向进程发送消息的时候,消息被送入到邮箱之中。当系统对receive语句进行求值的时候,就是对进程邮箱进行检查的唯一机会。
receive内部的工作机制:
1.当进入一个receive语句时,我们启动一个计时器(只有在表达式中存在after段时才会计时)。
2.从邮箱中读取一条消息,然后尝试对Pattern1,Pattern2等进行模式匹配,如果匹配成功,消息则从邮箱中删除,对应的模式之后的表达式就会被求值。
3.如果邮箱中的第一个消息不能匹配receive语句中的任何一个模式,那么会将第一个消息从邮箱中删除并送入一个“保存队列”。然后继续尝试从邮箱中取第二条消息,这个过程不断重复,知道找到匹配的模式,或者邮箱中的所有消息全部检查完毕。
4.如果邮箱中的所有消息都不匹配,那么就挂起进程,直到下一次又有新消息进入邮箱时在对该进程重新调度执行。当一个新消息到达时,只会对新消息进行匹配,不会对“保存队列”中的消息进行再次匹配。
5.一个消息如果匹配,那么存入“保存队列”中的所有消息就会按照他们到达进程的时间先后顺序重新放回邮箱中。这时,如果启动了一个计时器,那么这个计时器会被清空。
6.我们在等待消息的时候触发了计时器,那么对表达式ExpreTimeOut求值然后把存入“保存队列”的所有消息按照他们到达进程的时间先后顺序重新放回邮箱。

注册进程:
如果想向一个进程发送消息,就必须知道它的Pid,erlang提供了一种机制可以用于发布一个进程的标识符以便其他进程可以与之通信。这种进程叫做注册进程。
register(AnAtom,Pid):将一个进程Pid注册为一个名为AnAtom的原子。
unregister(AnAtom):移除AnAtom相应的进程的所有注册信息。如果一个进程死亡,那么它会自动被取消注册。
whereis(AnAtom) -> Pid|undefined:判断AnAtom是否已经被其他进程注册。
registered() -> [AnAtom::atom()]:返回一个系统中所有已经注册的进程名称列表。

时钟程序:

-module(clock).
-export([start/2,stop/0]).
start(Time,Fun) ->
register(clock,spawn(fun() -> tick(Time,Fun) end)).

stop() ->
clock ! stop.

tick(Time,Fun) ->
receive stop ->
void
after Time ->
Fun(),
tick(Time,Fun)
end.


并发程序template:

-module(template).
-compile(export_all).

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

rpc(Pid,Request) ->
Pid ! {self(),Request},
receive
{Pid,Response} ->
Response
end.
loop(X) ->
receive Any ->
io:format("~p~n",[Any]),
loop(X)
end.


尾递归技术:编译尾递归函数可以使在一系列语句最后的一个函数调用可以被替换为一个简单的跳转指令。指向被调用函数的开头。这就意味着一个尾递归函数可以无限循环而不需要消耗栈空间。

spawn(Mod,FunName,Args):创建一个新进程,Args是一个形如[arg1,arg2…]的参数列表,新进程会从函数Mod:FuncName(arg1,arg2…)开始执行。这样的方式创建进程,代码可以很好的进行动态更新。

一个进程如果使用exit(normal)退出或者运行完所有的代码然后退出被称为正常退出。

一个进程如果发生了运行时错误(例如除以0,试图调用一个不存在的函数),将因错误而结束,被称为异常退出,一个进程执行exit(Reason),Reason为一个除了atom normal之外的term,也被看做是异常退出。

一个进程可以设置到其他进程的链接(link),进程之间通过link(Other_Pid)来建立自身和Other_Pid进程的双向链接。当一个进程终止的时候,将给每一个与他建立了链接的进程发送一个信号,这个信号包括发送者的Pid和进程结束的原因。

缺省情况下,一个进程将忽略接收到的正常退出信号。
对于异常退出,缺省处理将忽略所有传递给接收进程的消息并且杀死该进程。
spawn_link在完成spawn函数功能的同时建立了一个与新创建的进程的链接。
我们可以修改一个进程接收到异常退出信号时的缺省退出行为,所有的信号都被转换为一个格式为{'EXIT',FromPid,Reason}的普通消息并且被添加到消息队列中。我们可以通过process_flag(trap_exit,true)来实现该功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值