【erlang】并发篇

PID类型

在之前的语法篇中,我们并没有介绍 PID这个类型,它和并发息息相关,因此我们在这里来学习它。

PID是进程标识符的意思,用来标识一个erlang进程。在所有相连的erlang节点中,PID都是唯一的。但是PID会被复用,当一个进程终止后,它的PID可以被其他进程再次使用。

进程ID用尖括号包裹的3个点分十进制表示 <x.y.z>,每个分量是一个十进制数字,一般 xz都是零,如果是一个远端主机上的进程的话,x就不为0了。

通过 self/0函数可以获取进程自己的PID,is_pid/1函数可以用来判断一个值是否是PID。

1> is_pid(1).
false
2> is_pid(<0.58.0>).
true
3> self().
<0.80.0>
4> is_pid(self()).
true

并发

erlang中并发的基本单元是函数,实现并发的函数是 spawn,它有两种调用方式:

  • spawn(Mod, Func, Args)
  • spawn(Fun)

spawn会在一个新的进程中执行指定的函数,注意这里所说的"进程"并不是操作系统的进程,而是erlang的进程,这种进程非常轻量,因此可以做到非常高的并发量。这两种方式的区别是第一种支持动态代码升级,后者不支持。

span函数的返回值就是进程PID,通过这个PID我们就可以给该进程发送消息了,这也是erlang中进程间通信的唯一方式。

发送消息

向一个进程发送消息的操作符是 !,语法为 Pid ! Message。该表达式的结果任然是 Message,这就意味着 Pid1 ! Pid2 ! ... ! Message也是合法的表示式,它会把 Message发送给 Pid1Pid2等所有进程。

消息是异步发送的,不会阻塞发送进程。

接收消息

接收消息的语法如下:

receive
    Pattern1 [when Guard] ->
        Expressions1;
    Pattern2 [when Guard2] ->
        Expressions2;
    ...
end

receive是阻塞式的,当消息到达时,会和每个模式进行匹配,并进行关卡验证,验证通过就会执行对应的表达式。receive只会接收一次消息,如果要循环接收,就需要使用递归。

超时

receive是阻塞式的,为了防止无限期等待,有时我们需要超时的功能。带超时的接收语法如下:

receive
    Pattern1 [when Guard] ->
        Expressions1;
    Pattern2 [when Guard2] ->
        Expressions2;
    ...
after Time ->
    Expressions
end

Time的单位是毫秒。超时时间有3种类型:

  • Time=0,立即超时。
  • Time=有限正数,正常超时。
  • Time=infinity,永不超时。

注意这里“等待”的含义并不是“等待消息的到来”,而是等待匹配的消息!这也就是说,即便是进程收到了消息,但是不能匹配接收语句的话,超时还是会执行。如果在超时之前,收到了与接收语句匹配的消息,那么超时语句就不会执行。

receive语句中可以只包含超时,它会让进程挂起一段时间,比如我们可以用它来实现一个sleep函数。

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

另一点需要注意的是,即便是时间为0的超时,在执行超时语句之前,进程也会先将自己邮箱中的消息与接收语句匹配一遍,如果能够匹配,那么超时也不会执行。我们可以用一个例子来说明这一点。

-module(chaoshi).

-export([test_recv/0]).

test_recv() ->
    receive
        yes -> io:format("yyds~n")
    after 0 -> 
        test_recv2()
    end.

在erlang shell中编译上面的代码,然后执行下面的命令:

2> Pid = spawn(chaoshi, test_recv, []).
<0.116.0>
3> Pid ! yes.
yyds
yes

如果不是先对邮箱中的消息进行匹配,那么yyds是永远不可能被打印的。

receive的流程

receive除了做消息匹配还会做消息管理和超时管理,一个典型的接收语句如下:

receive
    Pattern1 [when Gruad1] ->
        Expression1;
    Pattern2 [when Gruad1] ->
        Expression2;
    ...
after
    Time ->
        ExpressionTimeout
end.

它的工作流程如下:

  1. 启动一个定时器(如果有after的话)。

  2. 取出进程邮箱的第一条消息,与各个模式匹配,如果匹配成功,将消息从邮箱移除,并执行模式后的表达式。

  3. 如果第一个消息不能和任何一个模式匹配,系统会将它从进程邮箱移出,放入一个队列保存起来,然后继续尝试第二条消息,重复这一过程直到找到匹配的消息或者邮箱为空。

  4. 如果所有消息都不匹配,则进程被挂起并重新调度,直到有新消息时到达时,才继续匹配新消息,队列里的消息不会重新匹配。

  5. 如果新消息匹配成功,队列里的消息会按原顺序重新放入邮箱,如果启动了定时器,会取消定时器。

  6. 如果定时器先到期了,就会执行ExpressionTimeout,并将队列里的消息按原顺序重新放回邮箱。

注册进程

向进程发送消息就需要知道进程的PID,而要记住一个进程的PID是比较难的,而且每次启动进程PID都会变化。

注册进程就是将一个PID和一个原子关联起来,并通过某种方式公布出去,相当于给进程取个名字,这样其他进程通过这个名字就可以给这个进程发送消息了。DNS域名解析以及微服务的服务注册都是相同的套路。

与进程注册有关的函数有4个:

  • register(Name, Pid)
    Name(原子类型)与 Pid关联,如果 Name已被注册,此次注册会失败。一旦注册成功,就可以通过 Name ! Message向进程发送消息。
  • unregister(Name)
    取消注册,移出与 Name关联的所有注册信息。如果注册进程崩溃,会自动取消注册(这真是太棒了)。
  • whereis(Name)->Pid | undefined
    检查 Name是否已被注册,如果是就返回进程PID,否则返回 undefined
  • registered()->[Name::atom()]
    返回系统里所有注册进程的名称列表。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值