Erlang/Elixir Erlang 入门-模块和进程

DavidAlphaFox · 发布于 2017年12月26日 · 354 次阅读
84794b

什么是模块

在前面的教程中,编写的代码都是在Erlang的shell中进行的。而交互式shell也被认为是绝大部分动态语言的特性之一。但是,任何一个项目不可能都是靠在shell中输入代码完成的,那么代码必须保存在某处。保存的代码就是模块,模块就是一个包含了大量函数的文件,因为模块是文件,就一定要有一个名字。

如何编写一个模块

编写模块非常简单,只要打开任意一款文本编辑器就可以了(推荐使用VScode,当然如果你喜欢命令行我十分乐意的推荐你使用Emacs),然后将我们前面教程中的函数放入其中,但是这并不能形成一个模块。

Erlang定义一个模块必须满足下面两点

  1. 声明属性,这些属性描述模块自身的特质,例如模块的名字
  2. 声明函数。

声明函数已经完成了,那么如何声明属性呢?Erlang的模块一般会定义模块名和导出函数,定义方法如下

-module(Name). %% 声明模块名,模块名必须是原子
-export([Function1/Arity, Function2/Arity, ..., FunctionN/Arity]). %% 声明函数导出

下面就用就用Erlang 入门-命名函数中的greet函数编写一个msg模块:

-module(msg).
-export([greet/2]).

greet(male, Name) ->
  io:format("Hello, Mr. ~s!~n", [Name]);
greet(female, Name) ->
  io:format("Hello, Mrs. ~s!~n", [Name]);
greet(_, Name) ->
  io:format("Hello, ~s!~n", [Name]).

如何在shell使用模块

先假定msg.erl文件保存在/home/david/erl目录下,我们在这个目录下用erl命令打开Erlang的交互式shell

Erlang/OTP 20 [erts-9.1.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Eshell V9.1.5  (abort with ^G)
1>  c("msg").
{ok,msg}
2>msg:greet(male,"David").
Hello, Mr. David!
ok
3>

可以看到,当函数放到模块中的时候,调用函数就需要在函数前面增加模块名字和冒号,如msg:greet(male,"David").

什么是Erlang的进程

在Erlang中,最小执行单位是进程,当然这个进程并不是系统层面上的进程,也不是系统的线程线程,而是Erlang运行时中自己定义的一种轻量级的执行结构。

Erlang的进程是如何执行的

在上面提到Erlang的进程是Erlang运行时中定义的一种轻量级的执行结构,那么这个结构就是由Erlang的运行时来进行调度。Erlang运行时在启动之后会创建出和内核数量相同的系统线程,每个线程上都绑定了一个Erlang虚拟机的CPU,然后Erlang通过调度算法将就绪的Erlang进程调度到这个虚拟机的CPU上。

下面是调度逻辑的伪代码:

pid schedule() {
  static int majorReductions = MREDS;  // 计算自己执行多少个时钟
  majorReductions--; // 减少时钟
  if(majorReductions == 0) {  
    externalPoll(); // 时钟为0,检查外部事件,主要是socket
    majorReductions = MREDS;  // 重置时钟
   }  
  checkTimeouts(); // 检查超时
  pid p = nextReady(readyQueue); // 在准备就绪队列中找出就绪的进程 
  p->reductions = REDS;  // 给进程分配时间片
  p->status = RUNNING; // 标记运行
  return p; // 返回给CPU
}

是不是非常像一个操作系统在调度进程?因为Erlang的运行时,本身就在模拟一个特殊的基于寄存器的CPU,以及上面的一个任务调度器(最简单OS)。

如何创建一个Erlang进程

Erlang做为一个面向并发的计算机语言,它为我们提供了两个比较有意思的进程创建元语erlang:spawnerlang:spawn_link。这两个元语唯一的差别是erlang:spawn在创建一个Erlang进程成功后就不管这个进程了。而erlang:spawn_link会将创建者和被创建者关联起来,如果创建者异常退出了,被创建者也会跟着退出,反之亦然。

下面就用刚才建立好的msg模块演示下如何创建Erlang进程

Erlang/OTP 20 [erts-9.1.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Eshell V9.1.5  (abort with ^G)
1> c("msg").          
{ok,msg}
2> erlang:spawn(msg,greet,[male,"David"]).
Hello, Mr. David!
<0.67.0>
3> erlang:spawn_link(msg,greet,[male,"David"]).
Hello, Mr. David!
<0.69.0>
4> 

在这里spawn_link创建的进程因为是正常退出,并没有引起shell的崩溃,在后面介绍异常的时候,将会讲解Erlang进程的异常退出和如何应对。

Erlang进程的特点

Erlang为什么要这么大费周章的去设置这样一种进程机制呢?因为它有以下特点:

  1. 可抢占的软实时,这是Go,lua以及Akka库做不到的,毕竟Erlang最初设计是给电话交换机使用
  2. 轻量级,可以快速创建和大量创建,在CPU和内存充足的条件下,一个Erlang运行时环境可以创建上百万的Erlang进程
  3. 可监控,Erlang进程是可以通过监控机制进行管理,在异常退出的场景下,快速恢复。

总结

虽然已经讲述了如何进行进程创建了,但是大家很快就发现了吧,进程在执行完函数就立刻退出了,同时通过前面的文章知道Erlang中的变量是不可变的,那么Erlang进程要如何交换数据呢。在后面的文章,将会逐步介绍消息传递,异常处理等知识。

暂无回复。
需要 登录/注册 后方可回复, 如果你还没有账号请点击这里 注册