一:基本并发函数
下面是几个基本的并发函数:
(1)Pid = spawn(Mod,Func,Args)
(2)Pid = spawn(Fun)
创建一个新的并发进程来执行Fun()。这种形式的spawn总是使用被执行fun的当前值,而且这个fun无需从模块里导出。这两种spawn形式的本质区别与动态代码升级有关。
(3)pid ! Message
向标识符为Pid的进程发送消息Message。消息发送是异步的。发送方并不等待,而是会继续之前的工作。!被称为发送操作符。Pid ! M被定义为M。因此,Pid1 ! Pid2 ! ... ! Msg的意思是把消息Msg发送给Pid1、Pid2等所有进程。
(4)receive ... end
接收发送给某个进程的消息。它的语法如下:
receive
Patternl [when Guardl] ->
Expressions1;
Pattern2 [when Guard2] ->
Expressions2;
...
end
%% geometry.erl
area({rectangle, Width, Height}) -> Width * Height;
area({square, Side}) -> Side * Side.
%% area_server0.erl
-module(area servero).
-export([loop/0]).
loop() ->
receive
{rectangle, width, Ht}->
io:format("Area of rectangle is ~p~n",[width * Ht]),
loop();
{square,Side} ->
io:format("Area of square is ~p~n",[Side * Side]),
loop()
end.
1> Pid spawn(area_server0,Loop,[]).
<0.36.0>
2> Pid {rectangle,6,10}.
Area of rectangle is 60
{rectangle,6,10}
3> Pid!{square,12}.
Area of square is 144
{square,144}
loop() ->
receive
{rectangle, width, Ht}->
io:format("Area of rectangle is ~p~n",[width * Ht]),
loop()...
二:客户端——服务器介绍
Pid !{rectangle,6,10)
修改成下面这样:
Pid (self(), {rectangle, 6, 10})
self()是客户端进程的标识符。为了响应请求,我们必须把接收请求的代码从:
loop() ->
receive
{rectangle, width, Ht}->
io:format("Area of rectangle is ~p~n",[width * Ht]),
loop()...
修改成下面这样:
loop() ->
receive
{From, {rectangle, Width, Ht}}->
From ! Width * Ht,
loop();...
%% area_server1.erl
rpc(Pid, Request) ->
Pid ! {self(), Request},
receive
Response ->
Response
end.
把这些合并在一起,就能得到:
%% area_server1.erl
-module(area_server1).
-export([loop/0,rpc/2]).
rpc(Pid, Request)->
Pid {self(), Request},
receive
Response ->
Response
end.
loop() ->
receive
{From, {rectangle, width, Ht}} ->
From ! width * Ht,
loop();
{From, {circle, R}} ->
From ! 3.14159*R*R,
loop();
{From,Other} ->
From ! {error, Other},
loop()
end.
1> Pid spawn(area_serverl,loop,[]).
<0.36.0>
2> area_serverl:rpc(Pid, {rectangle,6,8}).
48
3> area_serverl:rpc(Pid,{circle,6}).
113.097
4> area_serverl:rpc(Pid,socks).
{error,socks}
loop()->
receive
{From, ...}->
From ! {self(), ...}
loop()
...
end
rpc(Pid,Request) ->
Pid ! {self(), Request},
receive
(Pid,Response} ->
Response
end.
%% area_server2.erl
-module(area server2).
-export([loop/0,rpc/2]).
rpc(Pid,Request)->
Pid ! {self(),Request},
receive
{Pid,Response} ->
Response
end.
loop() ->
receive
{From, {rectangle, width, Ht}} ->
From ! {self(), width * Ht},
loop();
{From, {circle, R}} ->
From ! {se1f(), 3.14159*R*R},
loop();
{From,other} ->
From ! {self(),{error,Other}},
loop()
end.
1> Pid = spawn(area_server2,loop,[])
<0.37.0>
2> area_server2:rpc(Pid,{circle,5}).
78.5397
%% area_server_final.erl
-module(area server final).
-export([start/0,area/2,loop/0]).
start() -> spawn(area_server_final,loop,[]).
area(Pid,What) ->
rpc(Pid,what).
rpc(Pid,Request) ->
Pid ! {self(),Request},
receive
{Pid,Response} ->
Response
end.
loop() ->
receive
{From,{rectangle,width,Ht}} ->
From ! {self(),width * Ht},
loop();
{From,{circle,R}} ->
From ! {5e1f(),3.14159*R*R},
loop();
{From,Other} ->
From ! {self(),{error,Other}},
loop()
end.
1> Pid area_server_final:start().
<0.36.0>
2> area_server_final:area(Pid,{rectangle,10,8}).
80
3> area_server_final:area(Pid,{circle,4}).
50.2654
三:进程很轻巧
%% processes.erl
-module(processes).
-export([max/1]).
%% max(N)
%% 创建N个进程然后销毁它们
%% 看看需要花费多长时间
max (N)->
Max = erlang:system_info(process limit),
io:format("Maximum allowed processes:~p~n",[Max]),
statistics(runtime),
statistics(wall clock),
L = for(1,N,fun() -> spawn(fun() -> wait() end) end),
{_, Timel} = statistics(runtime),
{_, Time2} = statistics(wall_clock),
lists:foreach(fun(Pid) -> Pid ! die end, L),
U1 = Time1*1000/N,
U2 = Time2*1000/N,
io:format("Process spawn time=~p (~p) microseconds~n",
[U1,U2]).
wait() ->
receive
die -> void
end.
for(N, N, F) -> [F()];
for(I, N, F) -> [F() | for(I+1, N, F)].
在Shell执行如下:
1> processes:max(20000).
就能看出创建进程的时间,随着进程数量的增加,进程分裂时间也在增加。如果继续增加进程的数量,最终会耗尽物理内存,导致系统开始把物理内存交换到硬盘上,运行速度明显变慢。当然,开多个进程会加大物理内存,但这与虚拟机内存无关,也可以说只要物理内存够大,erlang想建多少都不影响性能的。
四:带超时的接收
receive
Patternl [when Guardl] ->
Expressions1;
Pattern2 [when Guard2]->
Expressions2;
...
after Time ->
Expressions
end
1.只带超时的接收
%% lib_misc.erl
sleep(T) ->
receive
after T ->
true
end.
2.超时值为 0 的接收
%% lib_misc.erl
flush_buffer() ->
receive
_Any ->
flush_buffer()
after 0 ->
true
end.
%% lib_misc.erl
priority_receive() ->
receive
{alarm, X} ->
{alarm, X}
after 0 ->
receive
Any ->
Any
end
end.
3.超时值为无穷大的接收
4.实现一个定时器
%% stimer.erl
-module(stimer).
-export([start/2,cancel/1]).
start(Time,Fun) ->
spawn(fun() -> timer(Time, Fun) end).
cancel(Pid) -> Pid ! cancel.
timer(Time,Fun) ->
receive
cancel ->
void
after Time ->
Fun()
end.
1> Pid = stimer:start(5000,fun() -> io:format("timer event~n") end).
<0.42.0>
timer event
%% 我等待的时间超过了5秒钟,这样定时器就会触发。
%% 现在我将启动一个定时器,然后在到期前关闭它。
2> Pid1 = stimer:start(25000,fun() -> io:format("timer event~n") end).
<0.49.0>
3> stimer:cancel(Pidl).
cancel
五.选择性接收
receive
Patternl [when Guardl] ->
Expressions1;
Pattern2 [when Guard2] ->
Expressions2;
...
after
Time ->
ExpressionsTimeout
end
六:注册进程
如果想给一个进程发送消息,就需要知道它的PID,但是当进程创建时,只有父进程才知道它的PID。系统里没有其他进程知道它的存在。这通常很不方便,因为你必须把PID发送给系统里所有想要和它通信的进程。另一方面,这也很安全。如果不透露某个进程的PID,其他进程就无法以任何方式与其交互。Erlang有一种公布进程标识符的方法,它让系统里的任何进程都能与该进程通信。这样的进程被称为注册进程(registered process)。管理注册进程的内置函数有四个:
(1)register(AnAtom,Pid)
用 AnAtom (一个原子)作为名称来注册进程 Pid 。如果 AnAtom 已被用于注册某个进程,这次注册就会失败。(2)unregister(AnAtom)
移除与AnAtom关联的所有注册信息。
注: 如果某个注册进程崩溃了,就会自动取消注册。
(3)whereis(AnAtom) -> Pid | undefined
检查 AnAtom 是否已被注册。如果是就返回进程标识符 Pid ,如果没有找到与 AnAtom 关联的进程就返回原子undefined 。(4)registered() -> [AnAtom:atom()]
返回一个包含系统里所有注册进程的列表。
可以用register来改写第一节里的代码示例,并尝试用创建的进程名称进行注册:
1> Pid = spawn(area_servere,loop,[])
<0.51.0>
2> register(area,Pid).
true
3> area {rectangle,4,5}.
Area of rectangle is 20
{rectangle,4,5}
%% clock.erl
-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.
3> clock:start(5000, fun() -> io:format("TICK ~p~n",[erlang:now()]) end).
true
TICK {1164,553538,392266}
TICK {1164,553543,393084}
TICK {1164,553548,394083}
TICK {1164,553553,395064}
4>clock:stop().
stop
七:关于尾递归的说明
%% area_server_final.erl
loop() ->
receive
{From, {rectangle, width, Ht}} ->
From ! {self(),width * Ht},
loop();
{From,{circle,R}} ->
From ! {se1f(),3.14159*R*R},
loop();
{From,Other} ->
From {self(),{error,Other}},
loop()
end.
loop()->
receive
{From,{rectangle,Width,Ht}} ->
From ! {self(),Width * Ht},
loop(),
someotherFunc();
{From,{circle,R}} ->
From ! {se1f(),3.14159*R*R},
Loop();
...
end
end