Erlang 简要

世界是并行的,Erlang程序反应了我们思考和交流的方式,人作为个体通过发送消息进行交流,如果有人死亡,其他人会注意到。
Erlang里的模块类相当于OOPL中的类,进程相当于OOPL里的对象或类实例。
并发编程可以用来提升性能,创建可扩展和容错的系统,以及编写清晰和可理解的程序来控制现实世界里的应用。

并发程序是以一种并发编程语言编写的程序,并发编程语言拥有用于编写并发程序的语言结构。Erlang的并发程序是由互相通信的多组顺序进程组成,一个进程就是一个轻量级的虚拟机,可以执行单个的Erlang函数,只能通过发送和接收消息来与其他进程通信。也就是说,并发性是由Erlang虚拟机提供的,并操作系统的并发控制粒度要小很多。
在Erlang里:

  1. 创建和销毁进程是非常快的
  2. 在进程间发送消息是非常快的
  3. 进程在所有操作系统上都具有相同的行为方式
  4. 可以拥有大量的进程
  5. 进程间不共享内存,是完全独立
  6. 唯一的沟通方式是消息传递,每个进程都有一个邮箱与进程同步创建。

动态代码载入是Erlang特性之一,函数调用的总是最新模块里的最新版函数,哪怕当代码在模块里运行时重新编译了该模块也是如此。

基本元素操作

Erlang shell中,用句号加空格、tab或回车来结束表达式,%表示注释的起点,;隔离子句。模块是.erl 文件,库的头文件.hrl, shell中的编译时c(),外编译命令时erlc, 退出shell用q(),或erlang:halt().

变量以大写字母开头,且不能重新绑定变量,只能一次性赋值,具有不可变状态。原子是全局的,不需要宏定义或包含文件,以小写字母开头,还可放在单引号内,是极简表达式。

元组(tuple)是一些数量固定的项目归组成单一实体{,}, 由于是匿名的,通常在第一个元素上贴标签,来增加可读性。提取元组中的值使用模式匹配操作符=,为匿名变量,多个不必绑定相同的值。但是,_Mode则是常规变量。例如:

2> Family={family,father,mother,son}.
{family,father,mother,son}
3> {_,X,Y,Z}=Family.
{family,father,mother,son}
4> X.
father
5> Y.
mother
6> Z.
Son

记录(record)是元组的另一种形式,可以给元组的各个元素关联一个名称。使用记录的情形:
1) 用一些预先确定且数量固定的原子表示数据
2) 元素数量和元素名称不会随时间改变
3) 大元组中每个元组有相同的结构
采用#myrecord{k1=v1,k2=v2..}来创建record。

列表(list)形如[,,]可以存放任意数量的事物。Head可以是任何事物,Tail通常仍然是个列表。只要用[…|T]构建一个列表,就应确保T是一个列表。同样使用模式匹配来提取列表中的元素。列表推导的常规形式:

[X||Qualifier1,Qualifier2,…]

X是任意一表达式,限定符qualifier可以生成器,位串生成器或过滤器。生成器的写法

Pattern<- ListExpr

反转一个列表时,要调用lists:reverse.

Erlang中没有字符串,字符串是个整数列表,”HelloCloud”是一个列表的简写,io:format来指定打印输出。

映射组(map)是键值对的关联性集合,内部作为有序计划存储,适用情形:
1) 键不能预知时来表示键值对数据结构
2) 存在大量不同键来表示数据
3) 效率不重要时的万能数据结构
4) 自解释型数据结构
5) 用来表示键值解析树,如xml或配置文件
6) 用Json来通信

映射组的语法:

#{key1 op val1,key2 op val2,…,KeyN op valN}

'#后没有名称,op是=>或:=之一。 => 将现有键值更新为新值或给映射组增加一个新键值对。 :=用于更新。 健不能包含任何变量,值可以包含未绑定变量,在模式匹配成功后绑定。

映射组可以通过io:format 里的~p选项输出,并用io:read 或file:consult读取。
Maps:to_json(Map)->Bin 转化为二进制型json
Maps:from_json(bin)-> Nap, 将二进制json转化为map

Json与映射组的对应关系:
1) Json数字:Erlang的整数或浮点数
2) Json字符串:Erlang二进制型
3) Json列表:Erlang列表
4) true和false 对应
5) 映射组中的健必须是原子,字符串或二进制型,值必须用JSON的数据类型表示

模块与模式匹配

模块是Erlang的基本代码单元,erl文件编译后以.beam作为扩展名,采用UTF8字符集,.erl文件示意如下:

-module(模块名,与存放模块的文件名相同)
-export([方法名/输入参数的个数])
Method1( {a,b,c})->a*b*c;
Mehtod2({d,e})->d-e.  

模块属性有两种类型:预定义型和用户定义型。

Erlang中用于代表函数的数据类型被称为fun,相当于Python中的lambda,一般用于

  1.  对列表里的每个元素执行相同的操作
    
  2.  创建自己的控制
    
  3.  实现可重入解析代码,解析组合器或者lazy evaluator
    

模式匹配是Erlang的根基,case和if表达式使Erlang小而一致。

case Expression  of
         Pattern1[ when Guard1] -> Expr-seq1;
         Pattern2[when Guard2]-> Expr-seq2;
         …
end
if
         Guard1-> Expr_seq1;
         Guard2-> Expr_seq2;
         …
end

Erlang有两种方法来捕捉异常错误,一种是把抛出异常的调用函数封装在一个try_catch 表达式里,提供了概括信息,另一种是把调用封装在一个catch表达式里,提供了详细的栈跟踪信息。在捕捉到一个异常后,可以调erlang:get_stacktrace()来找到最近的栈信息。

把二进制型,位串,和位级模式匹配引入Erlang是为了简化网络编程。二进制型是置于双小于号和双大于号之间的一列整数或字符串。
例如: 1> Mybin1 = << “I LOVE YOU”>>

Term_to_bingary(Term) ->Bin 转换为二进制型
Binary_to_Term(Bin) ->Term 二进制型转换为Erlang的数据类型
精心选择宏的名称和Erlang代码布局,能最大限度地缩小C和Erlang的语义鸿沟。在Erlang里,最小的寻址单元是1位,位串里的位序列可直接访问。

运行

运行Erlang程序的方式:

  1.  在Erlang shell 中编译执行
    
  2.  Shell 脚本执行,例
    
Hello.sh
#!/bin/sh
Erl –noshell –pa /home/abel/practice/erlang/code –s hllstart –s init stop  
  1.  作为Escript 运行,例  
    
#!/usr/bin/env escript
Main(args)->
Io:format(“Hello world ~n”)

内置函数apply能调用某个模块的某个函数并传参。每个erlang进程都有一个被称为进程字典的私有数据存储区。为了增强类型的表达能力,可以用描述性变量给它们加上注解,类型规范为spec,类型说明type。通过dialyzer可以检查程序中的类型错误,最好写模块时先考虑类型并声明它们,然后编写代码。两个载入路径的函数:
-spec code:add_patha(Dir)=>true|{error:bad_directory} 载入路径头加入
-spec code:add_pathz(Dir)=>true|{error:bad_directory} 载入路径尾加入
通过os:cmd(command)可以在erlang中调用shell的脚本,查找标准库源码的命令code:which(file).

Make 是erlang的任务自动化工具,可以通过它来运行程序。下面是一个简单的makefile:

.SUFFIXES: .erl .beam
.erl .beam:
        erlc  -W $<
ERL = erl –boot start_clean
MODS = module1 module2 module3
all: compile
        $(ERL)  –pa    ‘home/abel/…/dir’–s module1 start
 
compile: ${MODS:%=%.beam}
 
clean:
        rm  -rf *.beam erl_crash.dump
 

如果Erlang程序崩溃了,会留下一个erl_crash.dump文件,可以通过web故障分析器来分析,命令如下:

1>    crashdump_viewer:start().

并发

Erlang中基本的并发函数
1) Pid =spwan(Mod,Func,Args) 创建一个新的进程来执行apply(Mod,Func,Args),与调用进程并列运行,会使用最新的代码定义模块。
2) Pid!Message 向Pid进程异步发送Message,!为发送操作符
3) Receive … end 接收消息

    receive
           Pattern1[when Guard1]-> Expression1;
           Pattern2[whenGuard2]->Expression2;
…
         aftertime->
                   Expressions
         end.

内置函数erlang:system_info(process_limit)可找出所允许的最大进程数,默认为262144.
进程注册的内置函数有:
register(AnAtom,Pid)用名称注册Pid
uregister(AnAtom) 注销关联注册
whereis(AnAtom)->Pid|undefined 检查Pid是否注册
registered()->[AnAtom::atom()]返回系统里所有注册进程的列表。

并发程序模板:

-module(ctemplate).
-compile(export_all).
 
start() ->
         Spwan(?MODULE,loop,[]).
 
rpc(Pid,Request) ->
         Pid! {self(),Request},
         receive
                   {Pid,Respone}->
                            Response
         end.
 
loop(X) ->
         receive
                   Any->
                            Io:format(“Received:~p ~n”, [Any]),
                            loop(X)
         end.

每当收到消息时会处理它并再次调用loop(),这一过程称为尾递归,无需消耗堆栈空间可以一直循环下去。

Erlang并发程序的错误处理建立在远程监测和处理错误的基础上,重点在补救而不是预防,几乎没有防御性代码,只有在错误后清理系统的代码,即让其他进程修复错误和任其崩溃。

程序在出错时立即崩溃的优点:
1) 不编写防御性代码,直接崩溃简单
2) 别人来修复
3) 不会使错误恶化
4) 第一时间举旗示意
5) 修复时不担心原因重在清理
6) 简化了系统架构
监视和连接类似,但监视是单向的,如果被监视的进程挂了会向监视进程发一“宕机“消息,而不是退出信号。基本错误处理函数有:

-spec spwan_link(Fun) ->Pid
-spec spwan_monitor(Fun)-> {Pid,Ref}
-spec process_flag(trap_exit,true)
-spec link(Pid) ->true
-spec unlink(Pid) -> true
-spec erlang:monitor(process,Item) ->Ref
-spec exit(Why) -> none()

分布式模型:分布式erlang 和基于socket的分布式模型。分布式erlang运行在可信网络,通常在同一局域网的集群上,并受防火墙保护。基于socket的分布式模型基于TCP/IP不可信网络.
分布式Erlang的主要问题在于客户端可以自行决定在服务器上分裂出多种进程,适合于你拥有全部的机器,并且想在单台机器上控制他们。lib_chan 模块让用户能够显式控制自己的机器分裂出哪些进程。

为了在互联网上执行并发程序:
1) 确保4369端口对TCP和UDP都开发,该端口保留给epmd(Erlang端口映射守护进程)
2) 选择1个或一段连续的端口给分布式erlang使用,确保这些端口开放,例如:

$erl -name …-setcookie … -kernelinet_dist_listen_min Min
Inet_dist_listen_maxMax

Rpc提供了许多远程调用服务,global里的函数可以用来在分布式系统里注册名称以及维护一个全连接的网络。
Erlang集群就是一组带有相同cookie的互连节点。创建cookie的三种方法:
1) 在文件$HOME/.erlang.cookie存放相同的cookie
2) 在Erlang启动时,可以用 –setcookie,例如

$erl  -setcookieABCDEFG2048

3) 内置函数erlang:set_cookie(node(),C)在程序中指定

Erlang通过名为端口的对象与外部程序通信,如果想端口发送一个消息,这一消息就会被发往与端口相连的外部程序,来自外部程序的消息会变成来自端口的Erlang消息。创建端口的进程成为端口的相连进程,所有发往端口的消息都必须标明相连进程的PID,所有来自外部程序的消息都会发往相连进程。

socket 编程简例

Erlang 中gen_tcp 用于编写TCP程序,gen_udp用于编写UDP程序。一个简单的TCP服务器echo示例:

Start_echo_server()->
         {ok,Listen}= gen_tcp:listen(1234,[binary,{packet,4},{reuseaddr,true},{active,true}]),
         {ok,socket}=get_tcp:accept(Listen),
         gen_tcp:close(Listen),
         loop(Socket).
 
loop(Socket) ->
         receive
                  {tcp,Socket,Bin} ->
                            io:format(“serverreceived binary = ~p~n”,[Bin])
                            Str= binary_to_term(Bin),
                            io:format(“server  (unpacked) ~p~n”,[Str]),
                            Reply= lib_misc:string2value(Str),
                            io:format(“serverreplying = ~p~n”,[Reply]),
                            gen_tcp:send(Socket,term_to_binary(Reply)),
                            loop(Socket);
                   {tcp_closed,Socket} ->
                            Io:format(“ServerSocket closed ~n”)
         end.

Tcp 的echo客户端示例:

echo_client_eval(Str) ->
         {Ok,Socket} = gen_tcp:connect(“localhost”,2345,[binary,{packet,4}]),
         ok= gen_tcp:send(Socket, term_to_binary(Str)),
         receive
                   {tcp,Socket,Bin}->
                            Io:format(“Clientreceived binary  = ~p~n”,[Bin]),
                            Val=binary_to_term(Bin),
                            io:format(“Clientresult = ~p~n”,[Val]),
                            gen_tcp:close(Socket)
         end.

UDP server示例

udp_demo_server(Port) ->
         {ok,Socket}= gen_udp:open(Open,[Binary]),
         loop(Socket).
Loop(Socket)->
         receive
                   {udp,Socket,Host,Port,Bin}->
                            BinReply= …,
                            gen_udp:send(Socket,Host,Port,BinReply),
                            loop(Socket)
         End.

UDP client 示例:

udp_demo_client(Request) ->
         {ok,Socket}= gen_udp:open(0,[Binary]),
         ok= gen_udp:send(Socket,”localhost”,1234,Request),
         Value=    receive
                                     {udp,Socket,_,_,Bin}-> {ok,Bin}
                            after2000 -> error
                            end,
         gen_udp:close(Socket),
         Value

注意,因为UDP是不可靠的,一定要设一个超时时间,而且Reqeust最好小于500字节。
WebSocket, JS 和Erlang相结合,能够实现Web的绝大多数功能。

OTP

OTP包含了一组库和实现方式,可以构建大规模、容错和分布式的应用程序,包含了许多强大的工具,能够实现H248,SNMP等多种协议,核心概念是OTP行为,可以看作一个用回调函数作为参数的应用程序框架,类似一个J2EE容器。行为负责解决问题的非函数部分,回调函数负责解决函数部分。

通过gen_server模块可以实现事物语义和热代码交换,

  1.  确定回调模块名
    
  2.  编写接口函数
    
  3.  在回调模块里编写6个必需的回调函数
    

当服务器崩溃时,需要一种机制来检测并重启它,要用到监测树,即创建一个监控器来管理服务器。监测树有两种:一对一和一对多。
$erl –boot start_sasl
会创建一个运行生产系统的环境,系统架构支持库(SASL,System Administration Support Libriaries)将负责错误记录和过载保护等工作。

使用gen_server, gen_supervisor,application等行为,可以构建可靠性为99.9999999的系统。
统一化的erlang消息:
1) 抽象了不同线路协议之间的区别
2) Erlang消息无需解析,接收进程不必先解析消息再处理,而http服务器就必须解析就收到的所有消息
3) Erlang消息可以包含任意复杂度的数据类型,而http消息必须被序列化成扁平化才能传输
4) Erlang消息可以在不同处理器之间传送
常见的第三方库有rebar(https://github.com/basho/rebar)和cowboy(https://githun.com/extend/cowboy)。 Rebar是管理erlang项目的事实标准,用户可以通过rebar创建新项目、编译项目、打包它们,以及把它们与其他项目整合在一起,同时集成了github。Cowboy是一个用erlang编写的高性能web服务器,是嵌入式web的热门实现。另外,库mochiweb2(http://github.com/mochi/mochiweb)的编码和解码方法可以实现json字符串和erlang数据类型的相互转换。

Erlang程序在多核CPU上运行
1) 使用大量进程
2) 避免副作用,例如不使用共享式ETS或DETS
3) 避免顺序瓶颈,可以选择pmap代替map
4) 小消息,大计算
5) 用mapreduce使计算并行化
mapreaduce是一个并行高阶函数,定义如下

  -specmapreduce(F1,F2,Acc0,L) ->Acc
         F1 = fun(Pid,X) ->void
         F2 = fun(Key,[Value],Acc0) ->Acc
         L = [X]
         Acc = X =term()  
       

Mapreduce 是在并行高阶函数(phofs)模块中定义的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,601评论 18 139
  • 在服务器端程序开发领域,性能问题一直是备受关注的重点。业界有大量的框架、组件、类库都是以性能为卖点而广为人知。然而...
    零一间阅读 864评论 0 12
  • 叶冉在后来的几日里追着我打听,相亲到底发生了什么。因为在这之前我颇雄心壮志的同她讲,新时代的女性要自立自由,我必定...
    毛彩虹阅读 376评论 1 5
  • 尚在車上時,我就已經受感於街景之肅穆。雖仍同前一日一般天暗著,又有閃爍的燈光縈繞,卻透著長長的肅靜,於此一條街上,...
    瓶蓋阅读 358评论 0 2
  • 每一句话都很舒服。
    苏醒love阅读 159评论 0 0