erlang 并发编程入门

本文介绍了Erlang的并发编程,包括其并发概念、基于进程的并发模型,以及spawn、send、receive等基本功能函数的使用。文章还涉及超时接受、进程注册、链接和监控等并发控制机制,展示了Erlang在并发编程中的优势和特性。
摘要由CSDN通过智能技术生成

回顾并发

首先需要解释两个概念,并发与并行:

并发:同一时间段内可以交替处理多个操作,强调同一时段交替发生

并行:同一时刻内同时处理多个操作,强调同一时刻点同时发生

如果我想要在C++中使用并发我应该怎么做呢——std::thread(如果笔者没记错的话,在C++11引入之前,多线程编程甚至需要靠编译平台提供额外的API才能实现)
如果在Java中则需要继承Thread类并实现Runnable接口
同时因为以上两种模型的资源都是在进程的各个线程中共享的,不可避免就出现了资源安全问题,以此带来了锁和钥匙的问题,对资源的访问就需要加锁和解锁的问题(比如java中stringbuffer和stringbuilder的区别)
而在erlang语言中,则不存在这种问题,因为erlang中,没有钥匙,更没有锁

如何并发

Erlang的并发是基于进程(process)的。进程是一些独立的小型虚拟机,他们有自己的内存(意味着数据的安全性,意味着不再需要锁和钥匙),可以执行Erlang函数。在Erlang中,process不在隶属于操作系统,而是编程语言(这在其他顺序型编程语言中显得不可思议)这就意味着Erlang的进程在任何操作系统上都会具有相同的逻辑行为,这样,就能编写可移植的并发代码,让它在任何支持Erlang的操作系统上运行。

基本功能函数

基于erlang语言的特性,并发编程便变得十分简单,只需要三个函数,他们是spawnsendreceive

spawn

如其他语言一样,你需要一个新的进程时自然需要去创建它,这个级别函数便是帮助你创建一个新的进程(就像在linux里fork了一下,一个崭新的进程便创建成功了)

Pid = spawn(Mod,Func,Args) %% Pid 此函数的返回值 进程标识符!
%% 使用此方法时切记 Mod中的Func 需要export
Pid = spawn(Fun) %% 传入匿名函数 该函数不需要导出

以上是spawn的两种使用方式,但是注意 得益于erlang 的热升级机制
请多使用MFA(第一种)方式,如果你需要升级你的代码的话
因为Func总是会调用最新的代码版本

send

erlang中的send使用的是!操作符,称之为发送操作符,使用方式如下

Pid!Message %% 此操作是异步的
如果你需要发送给多个进程 你可以这样操作:
Pid1!Pid2!Pid3!Message

receive

接收则需要进程使用邮箱(在进程创建时会同步创建),通过receive来读取邮箱中的信息

receive
	Message1 when WhatHappen ->
		Express1;
	Message2 ->
		Express2
end

并发编程实例

-module(study).
-export([get_result/0]).
%%-compile(export_all).

%% area({r,W,H}) ->
%%     W*H.
%% area({s,S})->
%%     S*S.

get_result() ->
    receive
        {r,W,H} ->
            io:format("~p~n",[W*H]);
        {s,S} ->
            io:format("~p~n",[S*S])
    end.

export_all标签会将所有函数导出,仅开发测试环境可用,正式环境中禁止使用此flag。
创建进程 传递信息
至此 我们完成了进程的创建 以及进程之前的消息传递。

超时接受

超时接受是receive的拓展。有时我们期望在一定的时间内接收到某条消息(消息的时效性问题),如果我们迟迟没有收到某条消息怎么办呢?erlang为我们提供了after条件,

get_result() ->
    receive
        {r,W,H} ->
            io:format("~p~n",[W*H]);
        {s,S} ->
            io:format("~p~n",[S*S])
    after 5000 ->
    	io:format("Something Bad Happen!")
    end.

超时触发
可以看到五秒后我们不再等待信息,而是执行after中的express。
如果Time为0会怎样? 会导致after里的主体立即触发,但是在这之前,系统仍然会对邮箱里的消息进行一次模式匹配
此外Time还可以设置为原子infinity,after则永远不会触发。
因此我们很容易想到使用after来制作定时器,在很多通信协议中,定时器和超时都是非常关键的东西,毕竟没有人希望无限期的等待下去。

注册进程

之前提到,想要与一个进程通信,自然需要这个进程的Pid,但是Pid是如何产生的?是我们在父进程中通过spawn产生的,也就意味着,只有父进程才知道这个新进程的Pid,这样对其他想要与新进程通信的进程是十分不便的,因为我们需要父进程告诉我他的子进程标识符我才能与之对话,为此erlang有一种公布进程标识符的方法,为进程注册(register)名称。

register(NameAtom,Pid)

功能如其单词含义,为Pid这个进程标识符的进程,以NameAtom(原子)为名称进行注册管理。如果这个NameAtom已经存在,则此处register会失败,但是一旦register成功,则可以通过NameAtom!message与该进程通信!
注册别名

unregister(NameAtom)

有注册自然会有注销,我们希望放弃某个进程别名时,可以通过此方法注销它
注销别名

需要注意的是,当这个注册过的进程崩溃时(这是常有的事),会自动取消注册

whereis(NameAtom)

当我们需要找某个进程时,自然便需要问一问这个进程是否存在
找指定进程

进程存在时返回进程标识符,不存在时返回undefined

registered()

那如果我们想知道目前系统中有多少注册进程怎么办呢,总不能一个一个去试吧。别担心,erlang提供了方法让我们知道目前有多少进程在线:
进程列表
可以看到已经有很多erlang的系统进程在线。

并发中的错误

在顺序语言编程中,我们往往强调防御式编程,因为如果这个进程因为某些原因出行异常导致终止,这往往是非常麻烦的一件事(比如windows因为critical process died导致的蓝屏)。但在erlang中,创建进程的内存开销以及时间消耗其实是非常小的,因此我们更多的是采取措施检测错误,然后等待错误发生,并纠正它。
Erlang在关于构建容错式软件的理念可以总结成:让其他进程修复错误任其崩溃

link(连接)

既然我希望能让其他进程知道本进程发生了错误崩溃了,那本进程自然希望其他的进程能够检查本进程的健康状态,通过link我能让别人知道本进程的健康状况,在本进程发生崩溃时能告知其他进程,反之亦然。
需要注意的点是,相连接的进程会组成一个进程组,如果有其中一个进程发生了崩溃,其他相连进程会因为进程组里进程的崩溃而一起崩溃
进程崩溃
如果希望阻止进程崩溃的扩散需要通过使用 process_flag(trap_exit, true) 使指定的进程转变为系统进程来阻止崩溃的扩散。
防火墙
连环崩溃
上图演示了当进程中有崩溃出现时,连接的进程会扩散奔溃的情况

monitor(监视)

监视和连接很相似,但它是单向的。如果本进程A监视进程B,而B出于某种原因终止了,就会向A发送一个“宕机”消息,但反过来就不行了。因为A能通过检查B的健康状况在B崩溃时接手B的任务,完成计算任务。这种思想在非常多的软件上都有体现(比如Redis中的哨兵模式)。
监视奔溃
上图演示了当子进程处于父进程的监视状态下,不会对其他进程造成连环崩溃影响。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值