笔记系列

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

向进程发送消息

receiveend

接收消息

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并发编程的更多相关文章

  1. Erlang入门(二)—并发编程

    Erlang中的process——进程是轻量级的,并且进程间无共享.查了很多资料,似乎没人说清楚轻量级进程算是什么概念,继续查找中...闲话不提,进入并发编程的世界.本文算是学习笔记,也可以说是< ...

  2. erlang并发编程(二)

    补充-------erlang并发编程 Pid =spawn(fun()-> do_sth() end). 进程监视: Ref = monitor(process, Pid)靠抛异常来终结进程 ...

  3. Erlang 101 Erlang环境和顺序编程

    笔记系列 Erlang环境和顺序编程 Erlang并发编程 Erlang分布式编程 Yaws Erlang/OTP 日期              变更说明2014-10-12 A outline, ...

  4. Erlang 103 Erlang分布式编程

    Outline 笔记系列 Erlang环境和顺序编程Erlang并发编程Erlang分布式编程YawsErlang/OTP 日期              变更说明 2014-11-23 A Outl ...

  5. Linux下基于Erlang的高并发TCP连接压力实验

    1.实验环境: 联想小型机: 操作系统:RedHat Enterprise LinuxServer release6.4(Santiago) 内核版本号:Linux server1 2.6.32-35 ...

  6. 《C++ 并发编程》- 第1章 你好,C++的并发世界

    <C++ 并发编程>- 第1章 你好,C++的并发世界 转载自并发编程网 – ifeve.com 本文是<C++ 并发编程>的第一章,感谢人民邮电出版社授权并发编程网发表此文, ...

  7. Python 的并发编程

    这篇文章将讲解 Python 并发编程的基本操作.并发和并行是对孪生兄弟,概念经常混淆.并发是指能够多任务处理,并行则是是能够同时多任务处理.Erlang 之父 Joe Armstrong 有一张非常 ...

  8. 并发-Java并发编程基础

    Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...

  9. Python并发编程-GIL全局解释器锁

    Python并发编程-GIL全局解释器锁 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.GIL全局解释器锁概述 CPython 在解释器进程级别有一把锁,叫做GIL,即全局解释 ...

随机推荐

  1. DAO

    DAO Data Access Object DAO(Data Access Object)是一个数据访问接口,数据访问:顾名思义就是与数据库打交道. 夹在业务逻辑与数据库资源中间. DAO模式是标准 ...

  2. 14.KVM安装之脚本和镜像目录树准备

    1.php脚本需要先安装PHP环境,Apache服务器必须支持PHP $ yum install -y php    #安装PHP $ php -v                      #查看是 ...

  3. mac 安装redis

    一.下载 官网http://redis.io/ (搞不懂为啥被墙) 二.安装 将下载的tar.gz文件复制到 /usr/local 文件夹下 解压 sudo tar -zxvf redis3.1.6. ...

  4. Krajee 文件上传

    http://plugins.krajee.com/file-input/demo#ajax-uploads 插件官网 项目要个好看点的上传控件,于是搜到了这个. git的地址是 https://gi ...

  5. Openfire开发配置,Openfire源代码配置,OpenFire二次开发配置

    原文:http://www.cnblogs.com/lixiaolun/archive/2013/12/07/3462780.html 1.下载源代码:http://www.igniterealtim ...

  6. .Net MVC 导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) 通过MVC控制器导出导入Excel文件(可用于java SSH架构)

    .Net MVC  导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) [原文地址] 通过MVC控制器导出导入Excel文件(可用于java SSH架构)   public cl ...

  7. View绘制--onMeasure() 、onLayout()

    绘制需要经过多次 measure() layout() 过程, measure:测量,不可被子类继承,调用onMeasure()方法 onMeasure():测量,测量结束后每一个View都保存了自己 ...

  8. HTML 认识

    1.1认识什么是纯文本文件 Window 自带的一个软件,叫做记事本,记事本保存的格式就是TXT,就是英文text的缩写,术语上称呼为"纯文本文件". 注意:    TXT文件,只 ...

  9. Android 开发平台的演变史

    Android开发平台的发展(并不是很懂) Eclipse 首先是由IBM的一个项目小组花了两年时间开发完成的,当时主要解决IBM开发工具 Visual Age for Java 和 WebSpher ...

  10. 如何使用 Quagga BGP(边界网关协议)路由器来过滤 BGP 路由

    在之前的文章中,我们介绍了如何使用 Quagga 将 CentOS 服务器变成一个 BGP 路由器,也介绍了 BGP 对等体和前缀交换设置.在本教程中,我们将重点放在如何使用前缀列表prefix-li ...