Erlang:带监视器的生成过程

Erlang: spawning process with monitor

我正在研究 Joe Armstrong 的 Programming Erlang 2nd E。本书每章末都有习题。第 13 章,练习 1 说:

Write a function my_spawn(Mod, Func, Args) that behaves like spawn(Mod, Func, Args) but with one difference. If the spawned process dies, a message should be printed saying why the process died and how long the process lived for before it died.

这是一个存在竞争条件的解决方案:

my_spawn(Mod, Func, Args) ->
    Pid = spawn(Mod, Func, Args),
    spawn(fun() ->
                  Ref = monitor(process, Pid),
                  T1 = erlang:monotonic_time(millisecond),
                  receive
                      {'DOWN', Ref, process, Pid, Why} ->
                          io:format("~p died because of ~p~n", [Pid, Why]),
                          io:format("~p lived for ~p ms~n", [Pid, erlang:monotonic_time(millisecond) - T1])
                  end
          end),
    Pid.

生成进程和创建监视器不是一个原子步骤,因此如果进程在生成之后但在创建监视器之前终止,我们将不会收到错误消息.

这是一个没有竞争条件的尝试:

my_spawn_atomic(Mod, Func, Args) ->
    spawn(fun() ->
                  {Pid, Ref} = spawn_monitor(Mod, Func, Args),
                  T1 = erlang:monotonic_time(millisecond),
                  receive {'DOWN', Ref, process, Pid, Why} ->
                          io:format("~p died because of ~p~n", [Pid, Why]),
                          io:format("~p lived for ~p ms~n", [Pid, erlang:monotonic_time(millisecond) - T1])
                  end
          end).

但是这个returns的PID是监控进程的,不是Func进程的。鉴于 spawn 总是 return 它创建的进程的 PID,似乎没有办法在不诉诸副作用的情况下 return Pid

实现原子生成的惯用方法是什么?

http://marcelog.github.io/articles/erlang_link_vs_monitor_difference.html

spawn_link 和spaw_monitor 的区别在这里很好的解释了。

-module(mon_test).
-export([my_spawn/3, die_in/1]).

my_spawn(Mod, Func, Args) ->
    spawn(my_spawn(mon_test, my_spawn, [self(), Mod, Func, Args]),
    receive
        Pid -> Pid
        after 1000 -> timeout
    end.

my_spawn(Parent, Mod, Func, Args) ->
    {Pid, Ref} = spawn_monitor(Mod, Func, Args),
    T1 = erlang:system_time(),
    Parent ! Pid,
    receive
       {'DOWN', Ref, _Any, Pid, Why} -> 
        io:format("~p died because of ~p, lived for ~p milliseconds~n", [Pid, Why, (erlang:system_time()-T1)/1000/1000])
    end.

die_in(Secs) ->
  receive 
    Reason -> exit(Reason)
    after Secs*1000 -> exit(timeout_reason)
end.


> mon_test:my_spawn(mon_test, die_in, [5]).
<0.155.0>
<0.155.0> died because of timeout_reason, lived for 5001.152 milliseconds

您可以将来自监控进程的 Pid 作为消息发送:

my_spawn_atomic(Mod, Func, Args) ->
    Parent = self(),
    MPid = spawn(fun() ->
                  {Pid, Ref} = spawn_monitor(Mod, Func, Args),
                  Parent ! {spawned, self(), Pid},
                  T1 = erlang:monotonic_time(millisecond),
                  receive {'DOWN', Ref, process, Pid, Why} ->
                          io:format("~p died because of ~p~n", [Pid, Why]),
                          io:format("~p lived for ~p ms~n", [Pid, erlang:monotonic_time(millisecond) - T1])
                  end
          end),
    receive
        {spawned, MPid, Pid} -> Pid
    after 1000 -> error % 1s should be way enough for spawning monitoring process
    end.

另一种选择是使用初始化阶段:

将函数包装在一个函数中
my_spawn(Mod, Func, Args) ->
    Pid = spawn(fun() ->
            receive run -> apply(Mod, Func, Args)
            after 1000 -> exit(init_timeout)
            end
        end),
    spawn(fun() ->
                  Ref = monitor(process, Pid),
                  T1 = erlang:monotonic_time(millisecond),
                  Pid ! run,
                  receive
                      {'DOWN', Ref, process, Pid, Why} ->
                          io:format("~p died because of ~p~n", [Pid, Why]),
                          io:format("~p lived for ~p ms~n", [Pid, erlang:monotonic_time(millisecond) - T1])
                  end
          end),
    Pid.