并发流程执行顺序

时间:2016-11-01 20:10:43

标签: concurrency process erlang

试图弄清楚Erlang并发是如何工作的。为了测试,我有以下模块:

server.erl:

-module(server).
-export([loop/0]).


loop() ->

    receive

        {foo, Msg_foo} ->
            io:format("~w~n", [Msg_foo]),
            loop();

        {bar, Msg_bar} ->
            io:format("~w~n", [Msg_bar]),
            loop();

        stop -> 
            io:format("~s~n", ["End server process"]),
            true

    end.

process_a.erl

-module(process_a).
-export([go_a/0]).

go_a() ->

    receive

        {foo, Pid1} ->
            Pid1 ! {foo, 'Message foo from process A'},
            go_a();

        {bar, Pid2} ->
            Pid2 ! {bar, 'Message bar from process A'},
            go_a()

    end.

process_b.erl

-module(process_b).
-export([go_b/0]).

go_b() ->

    receive

        {foo, Pid1} ->
            Pid1 ! {foo, 'Message foo from process B'},
            go_b();

        {bar, Pid2} ->
            Pid2 ! {bar, 'Message bar from process B'},
            go_b()

    end.

client.erl

-module(client).
-export([start/0]).
-import(server, [loop/0]).
-import(process_a, [go_a/0]).
-import(process_b, [go_b/0]).


go() ->

    Server_Pid = spawn(server, loop, []),

    Pid_A = spawn(process_a, go_a, []),
    Pid_B = spawn(process_b, go_b, []),

    Pid_A ! {foo, Server_Pid},
    Pid_B ! {bar, Server_Pid},

    Pid_A ! {bar, Server_Pid},
    Pid_B ! {foo, Server_Pid},

    Pid_A ! {foo, Server_Pid},
    Pid_B ! {foo, Server_Pid},

    Pid_A ! {bar, Server_Pid},
    Pid_B ! {bar, Server_Pid}.


start() ->
    go().

客户端向进程A和进程B发送消息,进程B又向服务器发送消息。消息的顺序是:

A foo
B bar
A bar
B foo
A foo
B foo
A bar
B bar

但程序输出为:

'Message foo from process A'
'Message bar from process A'
'Message foo from process A'
'Message bar from process A'
'Message bar from process B'
'Message foo from process B'
'Message foo from process B'
'Message bar from process B'

服务器首先处理来自进程A的所有消息,然后处理来自进程B的所有消息。我的问题是,确定消息处理顺序的是什么?我认为这是接收消息的顺序。

1 个答案:

答案 0 :(得分:4)

这一切都取决于流程安排。在客户端代码启动服务器并触发A和B之后,这些进程是新创建的,但可能甚至没有给出任何时间来执行(如果有的话,它们将立即暂停在它们的接收中)。客户端代码继续执行并快速向A和B发送一堆消息。这些是异步操作,客户端进程在从调用go()返回之前根本不需要挂起。

一旦暂停的进程收到消息,它就会准备好被安排执行,但在此之前可能需要一小段时间。同时,更多消息可能会一直到达其邮箱,因此当A或B实际开始运行时,它们可能已经从客户端的所有四条消息已经在其邮箱中。一般来说,你也可以不确定A和B中的哪一个将首先开始执行,即使在这样的简单情况下调度可能是非常可预测的。

所以在你的情况下,A在B之前被调度,它开始执行,并且在很短的时间内消耗它的所有消息。这不需要太多工作,所以A甚至不会花费整个时间片。然后由于其邮箱为空而暂停。然后B得到安排并做同样的事情。

如果有许多进程和/或大量工作,Erlang VM可能会在不同操作系统线程上的调度程序之间拆分进程(如果您有多核CPU,则以真正的并行方式运行)。但由于该示例非常简单,因此这些过程可能在单个调度程序中处理,因此排序变得更加可预测。如果A和B在其队列中都有数千条消息,或者每条消息需要花费大量的计算工作来处理,那么您会看到消息被交错。

(顺便说一下,你在客户端的导入声明什么都不做,因为你正在使用spawn(Module,Fname,Args)。如果你写了例如spawn(fun() - > loop()end)他们会需要。)