Erlang 102 Erlang并发编程
选项的含义
nosuspend 如果发送者需要暂停来发送,直接返回nosuspend
noconnect 如果发送前目标节点需要自动连接,直接返回noconnect
(5) erlang:send_after(Time, Dest, Msg) -> TimerRef
 
2024-10-19 12:40:58 原文笔记系列
Erlang环境和顺序编程
Erlang并发编程
Erlang分布式编程
Yaws
Erlang/OTP
日期 变更说明
2014-11-02 A outline
2014-11-03 A 1
2014-11-08 A 2 3
2014-11-17 A 4
Agenda
0 范围
Erlang的现实世界建模方式
Erlang进程创建
Erlang进程设计模式
Erlang进程错误处理
1 Erlang Concurrency Modeling Philosophy
Armstrong在[2]中跳出程序语言在处理并发应用场景时洋洋洒洒的急迫性、跃跃欲试的一站式解决方案阐述桎梏,创造性的提出“暂时忘却编程,思考一下现实世界里发生着什么”问题。
解决问题一直是CS科学家、IT工程师标榜的影响现实世界的方式,自计算机科学被质疑是否应该是一门学科/科学开始。图灵机、冯▪诺依曼机器,还有我不理解的量子机器等,真的是仿生学的产物或是对现实世界的模拟吗,但她们确确实实的成功了,并影响着领域内参与者的思维。用natural 还是artificial,似乎是taste的问题。不扯了!
将Armstrong的阐述摘录如下:
我们理解并发。
世界是并行的。
Erlang程序反映了我们思考和交流的方式。
人类表现为独立的个体,通过发送消息进行交流。
如果有人死亡,其他人会注意到。
Erlang concurrency constructor overview
Constructor |
Description |
self() |
返回所在执行进程的标识符 |
Pid = spawn(Mod, Func, Args) |
创建进程,该进程执行Mod:Func(Args) |
Pid ! Message |
向进程发送消息 |
receive … end |
接收消息 |
register(AnAtom, Pid) |
注册进程 |
unregister(AnAtom) |
移除进程注册名 |
whereis(AnAtom) -> Pid | undefined |
检测进程注册名 |
registered() -> [AnAtom::atom()] |
系统中所有注册进程的列表 |
link(Pid) |
调用进程与Pid之间设置双向链接 |
unlink(Pid) |
移除链接 |
spawn_link(Mod, Func, Args) |
原子操作:生成进程并设置双向链接 |
spawn_monitor(Mod, Func, Args) |
元组操作:生成进程并设置调用进程与新进程间的监控 |
process_flag(trap_exit, Flag) |
将调用进程设置为系统进程 |
Ref = erlang:monitor(process, Pid) |
生成对Pid的单向监控 |
erlang:demonitor(Reference) |
移除监控 |
erlang:demonitor(Reference, [flush]) |
移除监控,并清空监控进程邮箱中相关信息 |
exit(Reason) |
调用进程以原因Reason终止 |
exit(Pid, Reason) |
向进程Pid发送退出信号 |
2 创建进程
►Sample: echo server[1]
-module(echo). %% API -export([start/0, loop/0]). %%%=================================================================== %%% API %%%=================================================================== start() -> %Pid = spawn(echo, loop, []), %Pid ! {self(), hello}, register(echo, spawn(echo, loop, [])),%◄注册新生成的进程 echo ! {self(), hello},%◄向进程发送消息,消息不限于tuple,可以是任一term receive %◄当前进程阻塞式接收消息 %{Pid, Message} -> % io:format("~w~n", [Message]) {_Pid, Message} -> io:format("~w~n", [Message]) %end, %Pid ! stop. end. loop() -> receive {From, Message} -> From ! {self(), Message}, loop();%◄尾递归 stop -> true end. %%%=================================================================== %%% Internal functions %%%===================================================================
终端进程创建并注册echo进程后阻塞式接收消息;echo进程模式匹配接收消息,loop子句1中使用尾递归方式保持活跃。
没有找到其他合适的进程创建、执行交互的建模表示方式,暂以OO时序图表示:
执行
注意需要将创建进程用到的函数loop导出,否则会抛出如下错误:
原因嘛,自然是erts需要这些信息(MFA)来生成新进程。
一些有用的终端命令
Command |
Description |
pid(x,y,z) |
在终端中生成进程<x,y,z>的标识符 |
flush/0 |
检索和显示所有发送到终端进程信箱中的消息,同时删除信箱中的消息 |
i() |
输出当前运行时系统(ert)正在执行的进程,包括进程标识符、生成进程的函数 |
processes() |
输出所有系统中运行的进程pid列表 |
regs() |
输出所有已注册的进程 |
Erlang进程构造
Erlang的并发是基于进程的,这里进程隶属于Erlang语言,而不是底层操作系统中的进程。Erlang的进程是一些独立的小型虚拟机,可以执行Erlang函数[2]。
Erlang进程约束/特性
创建和销毁进程是非常快速的;
在进程间发送消息是非常快速的;
进程在所有操作系统中具有相同的行为方式;
可以拥有大量进程;
进程不共享任何内存,是完全独立的;
进程唯一的交互方式是消息传递。
基本函数
(1) spawn(Module, Function, Args) -> Pid
创建新的进程(子进程),新进程执行apply(Module, Function, Args)。子进程与父进程一起运行,Function(Args)必须导出。Spawn返回子进程的进程标识符Pid,可以用Pid给该进程发送消息。
新进程创建后,会使用最新的代码定义模块(有关Erlang软件升级/动态代码升级的概念,以后再阐述)。
(2) spawn(Function) -> Pid
创建新进程执行Function()。新进程总是使用被执行fun的当前值,fun不需要从模块中导出。
(3) Pid ! Message
向进程Pid异步的发送消息Message。该函数的返回结果为Message。
Pid1 ! Pid2 ! … !PidN ! Message的含义是Pid1! (Pid2 ! (… !(PidN ! Message)))。
以这种方式发送消息永远不会失败,但发送消息到不存在的进程会导致badarg错误,调用进程会结束执行。
(4) receive … end
(4.1)基本接收
语法
receive
Pattern1 [when Guard1] -> Expressions1;
Pattern2 [when Guard2] -> Expressions2;
…
end
语义
消息达到进程信箱后,依次尝试匹配Patterni [when Guardi],如果匹配则执行Expressionsi;否则消息驻留于进程信箱中以供以后处理,进程会开始等待下一条消息。(每个进程有一个随其创建而同步创建的信箱。)
(4.2)带超时的接收
语法
receive
Pattern1 [when Guard1] -> Expressions1;
Pattern2 [when Guard2] -> Expressions2;
…
after Time ->
Expressions
end
语义
进程进入接收表达式Time毫秒后,仍没有收到匹配的消息,进程停止等待消息,开始执行Expressions。Time为infinity时,回退为不带超时的接收。
(5)(un)register
register(atom(), Pid)
用原子作为Pid的注册名称,系统中任何进程可以通过该注册名与进程通信。
unregister(atom())
移除注册名称,进程终止后自动取消注册。
(6) lookup
whereis(atom()) -> Pid | undefined
检查名称是否被注册。
registered() -> [atom()]
返回系统中所有注册进程的名称列表。
进程的行为模式
父进程用一系列参数值作为子进程的初始状态,创建子进程。子进程携带状态数据,执行一个REPL(read evaluate print loop)函数保持活跃(例:echo中的loop)。在REPL函数中,模式匹配进程信箱中的消息,如果匹配,则做相应的内部处理、更新状态数据或结束进程活动;如果不匹配,则消息驻留于信箱中,或者执行错误处理函数来解决错误的消息发送问题。一般的,stop消息是通知子进程停止的信号。
这里堆砌Erlang进程创建构造的解释(来源:ERTS Reference Manual, Module erlang),目的是指出在基本函数/构造基础上Erlang的goose swimming,包括错误处理、一些特殊的消息传送方式;另一方面是提醒自己,在喊出”Huston, we have a problem!”之前,可以在文档中自己找到一些答案。
(1) spawn(Module, Function, Args)
Module = module()
Function = atom()
Args = [term()]
返回应用apply(Module, Function, Args)启动的新进程的pid。新创建的进程放入系统调度队列以待执行。
如果Module:Function/Arity不存在,新进程执行error_handler:undefined_function(Module, Function, Args)。这个错误处理器可以重新定义(见process_flag/2)。如果error_handler未定义,或用户重定义的error_handler未定义,则产生原因为undef的失败。
(2) register(RegName, PidOrPort) -> true
RegName = atom()
PidOrPort() = port() | pid()
将名称RegName与pid或端口标识符关联。RegName必须是atom,在发送操作符(!)中替代pid或端口标识符。
如果PidOrPort不存在、RegName已被使用、进程或端口已被注册、RegName是undefined时产生badarg失败。
Erlang除发送消息操作符(!)外,还支持一些特殊的消息发送方式,如下(完整的方式可详阅文档)
(3) erlang:send(Dest, Msg) -> Msg
Dest = dst()
Msg = term()
dst() = pid() | port() | (RegName::atom()) | {RegName::atom(), Node::node()}
与Dest ! Msg一致,发送消息Msg,返回值为Msg。
Dest可以是远程或本地pid、本地端口、本地注册名称、或另一个节点上的注册名称{RegName, Node}。
(4) erlang:send(Dest, Msg, Options) -> Res
Dest = dst()
Msg = term()
Options = [nosuspend | noconnect]
Res = ok | nosuspend | noconnect
dst() = pid() | port() | (RegName::atom()) | {RegName::atom, Node::node()}
发送消息,返回ok或不发送消息返回下面的选项值,除此之外与erlang:send/2相同。
选项的含义
nosuspend 如果发送者需要暂停来发送,直接返回nosuspend
noconnect 如果发送前目标节点需要自动连接,直接返回noconnect
(5) erlang:send_after(Time, Dest, Msg) -> TimerRef
Time = integer() >= 0 (0<= Time <= 4294967295)
Dest = pid() | atom()
Msg = term()
TimerRef = reference()
启动定时器,在Time毫秒后发送消息。
如果Dest是pid,pid必须是本地进程,进程pid不活跃或退出时,该定时器自动取消(该特性在erts 5.4.11中引入)。
如果Dest是atom,则应该是已注册进程的名称,该名称在传送消息时查找,如果名称未指向某个进程则产生错误。定时器不会自动取消。
参数不符合上述要求时产生badarg失败。
3 进程错误处理
Erlang关于构建容错式软件的基本思路是[2]:
(1) 让其他进程修复错误
应用中的进程相互监控的活跃状况,如果某个进程挂了,其他进程能够知道这一点,并采取相应的措施;
(2) 任其崩溃
将应用程序代码分为两部分:一部分负责解决问题(即关注于应用逻辑)、另一部分负责前一部分出现问题时纠正他们。纠正错误的代码一般是通用的、独立于应用的。
Erlang进程错误处理术语
(1) 系统进程
两类进程:普通进程、系统进程。spawn生成的进程是普通进程,普通进程调用BIF process_flag(trap_exit, true)变为系统进程(捕获退出信号)。
系统进程接收到退出信号时,会将信号转换为普通的{‘EXIT’, From, Reason}消息;而普通进程接收到错误信号时,如果退出信号原因不为normal会结束执行,并将退出信号传播到它链接的所有进程。
(2) 链接(link)
进程间相互链接,如果一个挂了,会向另一个发送错误信号。
(3) 监视(monitor)
单向的链接,如果被监视进程挂了,会向监视进程发送’DOWN’消息。
(4) 错误信号
进程间协作的方式是传递消息或错误信号。消息是通过send(!)发送的,错误信号是进程崩溃或正常终止时自动发送的。
基本错误处理函数
(1) link(Pid)
调用进程与Pid设置双向链接。
如果Pid不存在,抛出noproc退出异常。
多次调用没有副作用。
(2) monitor(process, PidOrRegName) -> Reference
生成对进程Pid/RegName的单向监控。
如果调用是,PidOrRegName已经终止或不存在,则会向监视进程发送退出原因是noproc的’DOWN’消息。
多次调用生成不同的Reference。
‘DOWN’消息的格式[Module erlang doc]
{‘DOWN’, Reference, process, Object, Info}
Object: 被监视对象的引用,Info:进程退出原因、noproc(不存在的进程)或noconnection(无法连接节点)。
(3) process_flag(trap_exit, Flag)
将当前进程的退出信号转换为退出消息。
(4) spawn_link spawn_monitor
创建进程和建立链接/监视的原子操作。
(5) unlink demonitor
解除链接/监视关系。
(6) exit
如果不在catch/try中调用exit(Reason),当前进程会结束执行,并向其链接的所有进程传播退出信号{‘EXIT’, From, Reason}。Reason可以是任意term。
exit(OtherPid, Reason)向OtherPid进程/端口发送原因为Reason的退出新信号,不同的Reason时OtherPid的行为:
(a) Reason不是normal或kill
如果OtherPid不捕获退出信号,将以Reason退出;否则,退出信号转换为消息{‘EXIT’, From, Reason}加入OtherPid的信箱(消息队列),From是发送退出信号的进程;
(b) Reasone是normal
OtherPid不会退出,如果捕获退出信号,退出信号转换为消息{‘EXIT’, From, normal}并将如OtherPid的信箱;
(c) Reason是kill
OtherPid以killed原因无条件结束执行。
4 进程设计模式
文[3]中指出,在出现OTP之后,使用原生的进程和消息传递编写Erlang应用代码,反而变为一个高级话题。当然,借助框架、基础设施来解决常见的应用开发问题、甚至捎带保证了产品级代码的高可靠性和性能等要求,但是必要的时候,make hands dirty才是必杀技。(想起了Doug Lee大叔4章的 Java并发编程:设计原则和模式,我所认识的Java程序员几乎没有读过这本书,SSH就像煎饼包一切一样似乎可以解决所有问题,真的吗?)
[1]中列出了四种最常见的进程设计模式:
(1) 客户端/服务器
通常应用于资源分配的进程。服务端代码和客户端代码位于同一module中,通信格式契约在接收消息模式匹配中体现。
(2) 有限状态机(finite state machine ,FSM)
需要维护一个活动的状态,状态的变迁通过事件(消息)触发。
(3) 事件句柄(event handler)
接收特定类型的消息,再做相应的处理。概念上与OO设计模式中Publisher/Observer基本一致。
(4) 监控进程(supervisor)
建立和维护应用中进程结构树,监控进程负责启动工作进程并监控。
监控进程的通用行为是启动工作进程,监控他们,在必要时重新启动工作进程;
监控进程的特定行为是何时、如何启动和重新启动工作进程。
通用行为和特定行为的区分与OO设计模式中template method方法的思想不谋而合。
►(1)Sample: frequency[1]
-module(frequency). %% API -export([start/0, stop/0, allocate/0, deallocate/1]). -export([init/0]). %%%=================================================================== %%% API %%%=================================================================== % server side function start() -> register(frequency, spawn(frequency, init, [])). init() -> Frequencies = {[10,11,12,13,14,15],[]}, loop(Frequencies). % client side function stop() -> call(stop). allocate() -> call(allocate). deallocate(Freq) -> call({deallocate, Freq}). call(Message) -> frequency ! {request, self(), Message}, receive {reply, Reply} -> Reply end. loop(Frequencies) -> receive {request, Pid, allocate} -> {NewFrequencies, Reply} = allocate(Frequencies, Pid), reply(Pid, Reply), loop(NewFrequencies); {request, Pid, {deallocate, Freq}} -> NewFrequencies = deallocate(Frequencies, Freq), reply(Pid, ok), loop(NewFrequencies); {request, Pid, stop} -> reply(Pid, ok) end. reply(Pid, Reply) -> Pid ! {reply, Reply}. %%%=================================================================== %%% Internal functions %%%=================================================================== % the helper function used to allocate and deallocate frequencies allocate({[], Allocated}, _Pid) -> {{[], Allocated}, {error, no_frequency}}; allocate({[Freq|Free], Allocated}, Pid) -> {{Free, [{Freq, Pid} | Allocated]}, {ok, Freq}}. deallocate({Free, Allocated}, Freq) -> NewAllocated = lists:keydelete(Freq, 1, Allocated), {[Freq|Free], NewAllocated}.
执行
描述
服务端持有频率(frequency)资源,客户端申请分配频率,并在不使用时很合作的释放频率。
有一点很奇怪,客户端可以关闭服务端。这里出于演示的目的,只有从终端(客户端)中关闭Server。
消息封装,客户端代码没有必要暴露任何内部通信协议细节,客户端可见的是工作的简单的API。
lists:keydelete(Module lists)
keydelete(Key, N, TupleList1) -> TupleList2
Types:
Key = term()
N = integer() >= 1
1..tuple_size(Tuple)
TupleList1 = TupleList2 = [Tuple]
Tuple = tuple()
Returns a copy of TupleList1 where the first occurrence of a tuple whose Nth element compares equal to Key is deleted, if there is such a tuple.
如果元组列表TupleList1中存在这样的元组,第N个元素等于Key,返回删除该元组后的TupleList1。
最后,启动两个终端产生两个运行时系统,证据如下:
►(2)Sample: phone[1]
Left blank |
[1]中列举的固定电话示例在当前来看是一个很大很大的坑,经过一番折腾后,发觉需要纳入考虑的活动体包括:两个人/terminal、两个电话、电话线路broker,而这些放在分布式编程中更为合适:
故,这里仅提示一下,将每个状态表示为函数,函数体中receive状态迁移消息并做相应处理,状态迁移以尾部调用另一个状态函数完成。
I promise, I shall return! J
►(3)Sample: event_manger[1]
event_manger.erl
-module(event_manager). %% API -export([start/2, stop/1]). -export([add_handler/3, delete_handler/2, get_data/2, send_event/2]). -export([init/1]). %%%=================================================================== %%% API %%%=================================================================== start(Name, HandlerList) -> register(Name, spawn(event_manager, init, [HandlerList])), ok. init(HandlerList) -> loop(initialize(HandlerList)). stop(Name) -> Name ! {stop, self()}, receive {reply, Reply} -> Reply end. add_handler(Name, Handler, InitData) -> call(Name, {add_handler, Handler, InitData}). delete_handler(Name, Handler) -> call(Name, {delete_handler, Handler}). get_data(Name, Handler) -> call(Name, {get_data, Handler}). send_event(Name, Event) -> call(Name, {send_event, Event}). %%%=================================================================== %%% Internal functions %%%=================================================================== loop(State) -> receive {request, From, Msg} -> {Reply, NewState} = handle_msg(Msg, State), reply(From, Reply), loop(NewState); {stop, From} -> reply(From, terminate(State)) end. initialize([]) -> []; initialize([{Handler, InitData} | Rest]) -> [{Handler, Handler:init(InitData)} | initialize(Rest)]. terminate([]) -> []; terminate([{Handler, Data} | Rest]) -> [{Handler, Handler:terminate(Data)} | terminate(Rest)]. handle_msg({add_handler, Handler, InitData}, LoopData) -> {ok, [{Handler, Handler:init(InitData)} | LoopData]}; handle_msg({delete_handler, Handler}, LoopData) -> case lists:keysearch(Handler, 1, LoopData) of false -> {{error, instance}, LoopData}; {value, {Handler, Data}} -> Reply = {data, Handler:terminate(Data)}, NewLoopData = lists:keydelete(Handler, 1, LoopData), {Reply, NewLoopData} end; handle_msg({get_data, Handler}, LoopData) -> case lists:keysearch(Handler, 1, LoopData) of false -> {{error, instance}, LoopData}; {value, {Handler, Data}} -> {{data, Data}, LoopData} end; handle_msg({send_event, Event}, LoopData) -> {ok, event(Event, LoopData)}. event(_Event, []) -> []; event(Event, [{Handler, Data} | Rest]) -> [{Handler, Handler:handle_event(Event, Data)} | event(Event, Rest)]. call(Name, Msg) -> Name ! {request, self(), Msg}, receive {reply, Reply} -> Reply end. reply(To, Msg) -> To ! {reply, Msg}.
io_handler.erl
-module(io_handler). %% API - callbacks -export([init/1, terminate/1, handle_event/2]). %%%=================================================================== %%% API %%%=================================================================== init(Count) -> Count. terminate(Count) -> {count, Count}. handle_event({raise_alarm, Id, Alarm}, Count) -> print(alarm, Id, Alarm, Count), Count+1; handle_event({clear_alarm, Id, Alarm}, Count) -> print(clear, Id, Alarm, Count), Count+1; handle_event(_Event, Count) -> Count. %%%=================================================================== %%% Internal functions %%%=================================================================== print(Type, Id, Alarm, Count) -> Date = fmt(date()), Time = fmt(time()), io:format("#~w,~s,~s,~w,~w,~p~n", [Count, Date, Time, Type, Id, Alarm]). fmt({AInt, BInt, CInt}) -> AStr = pad(integer_to_list(AInt)), BStr = pad(integer_to_list(BInt)), CStr = pad(integer_to_list(CInt)), [AStr, $:, BStr, $:, CStr]. pad([M1]) -> [$0, M1]; pad(Other) -> Other.
log_handler.erl
-module(log_handler). %% API - callbacks -export([init/1, terminate/1, handle_event/2]). %%%=================================================================== %%% API %%%=================================================================== init(File) -> {ok, Fd} = file:open(File, write), Fd. terminate(Fd) -> file:close(Fd). handle_event({Action, Id, Event}, Fd) -> {MegaSec, Sec, MicroSec} = now(), io:format(Fd, "~w,~w,~w,~w,~w, ~p~n", [MegaSec, Sec, MicroSec, Action, Id, Event]), Fd; handle_event(_, Fd) -> Fd. %%%=================================================================== %%% Internal functions %%%===================================================================
运行示例
按照Publisher/Observer设计模式,publish的事件(send_event)被所有Observer(io_handler, log_handler)接收并处理。
►(4)Sample: my_supervisor[1]
my_supervisor.erl 监控者/进程
-module(my_supervisor). %% API -export([start_link/2, stop/1]). -export([init/1]). %%%=================================================================== %%% API %%%=================================================================== start_link(Name, ChildSpecList) -> register(Name, spawn_link(my_supervisor, init, [ChildSpecList])), ok. init(ChildSpecList) -> process_flag(trap_exit, true), loop(start_children(ChildSpecList)). stop(Name) -> Name ! {stop, self()}, receive {reply, Reply} -> Reply end. %%%=================================================================== %%% Internal functions %%%=================================================================== start_children([]) -> []; start_children([{M, F, A} | ChildSpecList]) -> case (catch apply(M, F, A)) of {ok, Pid} -> [{Pid, {M, F, A}} | start_children(ChildSpecList)]; _ -> start_children(ChildSpecList) end. loop(ChildList) -> receive {'EXIT', Pid, _Reason} -> NewChildList = restart_child(Pid, ChildList), loop(NewChildList); {stop, From} -> From ! {reply, terminate(ChildList)} end. restart_child(Pid, ChildList) -> {value, {Pid, {M, F, A}}} = lists:keysearch(Pid, 1, ChildList), {ok, NewPid} = apply(M, F, A), [{NewPid, {M, F, A}} | lists:keydelete(Pid, 1, ChildList)]. terminate([{Pid, _} | ChildList]) -> exit(Pid, kill), terminate(ChildList); terminate(_ChildList) -> ok.
add_two.erl 工作者
-module(add_two). %% API -export([start/0, request/1, loop/0]). %%%=================================================================== %%% API %%%=================================================================== start() -> process_flag(trap_exit, true), Pid = spawn_link(add_two, loop, []), register(add_two, Pid), {ok, Pid}. request(Int) -> add_two ! {request, self(), Int}, receive {result, Result} -> Result; {'EXIT', _Pid, Reason} -> {error, Reason} after 1000 -> timeout end. loop() -> receive {request, Pid, Msg} -> Pid ! {result, Msg + 2} end, loop(). %%%=================================================================== %%% Internal functions %%%===================================================================
工作者运行示例
监控模式运行示例
局限性
监控进程应该托管工作进程的创建。
Now, let us face the crucial distributed world!
参考文献
[1] Cesarini F., Thompson S.著,慕尼黑Isar工作组 杨剑译.
Erlang编程指南.
北京: 机械工业出版社.2011.
[2] Armstrong J.著,牛化成 译.
Erlang程序设计(第2版).(Programming Erlang, Second Edition – Software for a Concurrent World).
北京: 人民邮电出版社.2014.
[3] Logan M., Merritt E., Carlsson R.著,连城 译.
Erlang/OTP并发编程实战.(Erlang and OTP in Action).
北京: 人民邮电出版社.2012.
Erlang 102 Erlang并发编程的更多相关文章
- Erlang入门(二)—并发编程
Erlang中的process——进程是轻量级的,并且进程间无共享.查了很多资料,似乎没人说清楚轻量级进程算是什么概念,继续查找中...闲话不提,进入并发编程的世界.本文算是学习笔记,也可以说是< ...
- erlang并发编程(二)
补充-------erlang并发编程 Pid =spawn(fun()-> do_sth() end). 进程监视: Ref = monitor(process, Pid)靠抛异常来终结进程 ...
- Erlang 101 Erlang环境和顺序编程
笔记系列 Erlang环境和顺序编程 Erlang并发编程 Erlang分布式编程 Yaws Erlang/OTP 日期 变更说明2014-10-12 A outline, ...
- Erlang 103 Erlang分布式编程
Outline 笔记系列 Erlang环境和顺序编程Erlang并发编程Erlang分布式编程YawsErlang/OTP 日期 变更说明 2014-11-23 A Outl ...
- Linux下基于Erlang的高并发TCP连接压力实验
1.实验环境: 联想小型机: 操作系统:RedHat Enterprise LinuxServer release6.4(Santiago) 内核版本号:Linux server1 2.6.32-35 ...
- 《C++ 并发编程》- 第1章 你好,C++的并发世界
<C++ 并发编程>- 第1章 你好,C++的并发世界 转载自并发编程网 – ifeve.com 本文是<C++ 并发编程>的第一章,感谢人民邮电出版社授权并发编程网发表此文, ...
- Python 的并发编程
这篇文章将讲解 Python 并发编程的基本操作.并发和并行是对孪生兄弟,概念经常混淆.并发是指能够多任务处理,并行则是是能够同时多任务处理.Erlang 之父 Joe Armstrong 有一张非常 ...
- 并发-Java并发编程基础
Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...
- Python并发编程-GIL全局解释器锁
Python并发编程-GIL全局解释器锁 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.GIL全局解释器锁概述 CPython 在解释器进程级别有一把锁,叫做GIL,即全局解释 ...
随机推荐
- DAO
DAO Data Access Object DAO(Data Access Object)是一个数据访问接口,数据访问:顾名思义就是与数据库打交道. 夹在业务逻辑与数据库资源中间. DAO模式是标准 ...
- 14.KVM安装之脚本和镜像目录树准备
1.php脚本需要先安装PHP环境,Apache服务器必须支持PHP $ yum install -y php #安装PHP $ php -v #查看是 ...
- mac 安装redis
一.下载 官网http://redis.io/ (搞不懂为啥被墙) 二.安装 将下载的tar.gz文件复制到 /usr/local 文件夹下 解压 sudo tar -zxvf redis3.1.6. ...
- Krajee 文件上传
http://plugins.krajee.com/file-input/demo#ajax-uploads 插件官网 项目要个好看点的上传控件,于是搜到了这个. git的地址是 https://gi ...
- Openfire开发配置,Openfire源代码配置,OpenFire二次开发配置
原文:http://www.cnblogs.com/lixiaolun/archive/2013/12/07/3462780.html 1.下载源代码:http://www.igniterealtim ...
- .Net MVC 导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) 通过MVC控制器导出导入Excel文件(可用于java SSH架构)
.Net MVC 导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) [原文地址] 通过MVC控制器导出导入Excel文件(可用于java SSH架构) public cl ...
- View绘制--onMeasure() 、onLayout()
绘制需要经过多次 measure() layout() 过程, measure:测量,不可被子类继承,调用onMeasure()方法 onMeasure():测量,测量结束后每一个View都保存了自己 ...
- HTML 认识
1.1认识什么是纯文本文件 Window 自带的一个软件,叫做记事本,记事本保存的格式就是TXT,就是英文text的缩写,术语上称呼为"纯文本文件". 注意: TXT文件,只 ...
- Android 开发平台的演变史
Android开发平台的发展(并不是很懂) Eclipse 首先是由IBM的一个项目小组花了两年时间开发完成的,当时主要解决IBM开发工具 Visual Age for Java 和 WebSpher ...
- 如何使用 Quagga BGP(边界网关协议)路由器来过滤 BGP 路由
在之前的文章中,我们介绍了如何使用 Quagga 将 CentOS 服务器变成一个 BGP 路由器,也介绍了 BGP 对等体和前缀交换设置.在本教程中,我们将重点放在如何使用前缀列表prefix-li ...