Elixir의 프로세스는 서로 격리되어 있고 기본적으로 아무것도 공유하지 않아요. 링크되지 않은 자식 프로세스가 크래시해도 부모 프로세스는 영향을 받지 않아요.
parent_pid =
spawn(fn ->
spawn(fn -> raise "oops" end)
receive do
message -> IO.inspect(message, label: "received message")
end
end)
# => 20:03:08.405 [error] Process #PID<0.153.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
Process.alive?(parent_pid)
# => true
이 동작은 프로세스들을 서로 _링크_하는 것으로 바꿀 수 있어요. 두 프로세스가 링크되어 있으면, 한 프로세스의 실패가 다른 프로세스로 전파돼요. 링크는 양방향이에요.
parent_pid =
spawn(fn ->
spawn_link(fn -> raise "oops" end)
receive do
message ->
IO.inspect(message, label: "received message")
end
end)
# => 20:05:34.125 [error] Process #PID<0.171.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
Process.alive?(parent_pid)
# => false
프로세스는 원자적 연산인 spawn_link/1을 사용해 호출한 프로세스에 이미 링크된 상태로 생성할 수 있어요. 아니면 나중에 Process.link/1로 링크할 수도 있어요.
Process.info/1로 어떤 프로세스가 현재 그 프로세스에 링크되어 있는지 (그리고 아직 실행 중인지) 확인할 수 있어요.
pid = spawn_link(fn -> :timer.sleep(50_000) end)
# => #PID<0.126.0>
self()
|> Process.info()
|> Keyword.get(:links)
# => [#PID<0.126.0>]
프로세스 링크는 병렬로 작업을 처리할 때 유용해요. 한 작업 묶음이 끝나지 못하면 다른 작업 묶음도 계속 진행하지 않아야 하는 상황에서 말이에요.
링크는 프로세스를 _감독_하는 데에도 사용할 수 있어요. 어떤 프로세스가 _exit를 트랩_하고 있으면, 링크된 프로세스가 크래시해도 함께 크래시하지 않아요. 대신 크래시에 대한 메시지를 받아요. 그래서 예를 들어 크래시한 프로세스를 다시 시작하는 식으로, 크래시에 우아하게 대응할 수 있어요.
프로세스는 Process.flag(:trap_exit, true)를 호출해서 exit를 트랩하도록 설정할 수 있어요. Process.flag/2는 플래그의 새 값이 아니라 이전 값을 반환한다는 점에 주의해요.
플래그의 현재 값은 Process.info/1로 확인할 수 있어요.
pid
|> Process.info()
|> Keyword.get(:trap_exit)
# => false
링크된 프로세스가 크래시했을 때 그 프로세스로 보내지는 메시지는 {:EXIT, from, reason} 패턴과 일치해요. 여기서 from은 PID예요. reason이 :normal 원자가 아니면, 그 프로세스가 크래시했거나 강제로 종료되었다는 뜻이에요.
parent_pid =
spawn(fn ->
Process.flag(:trap_exit, true)
spawn_link(fn -> raise "oops" end)
receive do
message ->
IO.inspect(message, label: "received message")
# do something else,
# to demonstrate that the process stays alive
:timer.sleep(50_000)
end
end)
# => 20:05:34.125 [error] Process #PID<0.295.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
# received message: {:EXIT, #PID<0.295.0>,
# {%RuntimeError{message: "oops"},
# [{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 678]}]}}
Process.alive?(parent_pid)
# => true
exit를 트랩하면 그 프로세스는 링크된 모든 프로세스의 크래시에 영향을 받지 않게 되고, 읽지 않은 exit 메시지는 프로세스의 메일박스에 쌓일 수 있어요. 그래서 exit를 트랩하는 프로세스에 링크된 모든 프로세스를 추적해야 해요. 보통 exit를 트랩하는 프로세스는 다른 프로세스를 감독하는 일만 하고 그 외에는 아무것도 하지 않아요.
실제로는 감독자 프로세스를 처음부터 직접 구현하지 않아요. Elixir는 이런 기능을 Supervisor behaviour 형태의 추상화로 제공해요.
프로세스와 관련된 다른 추상화들, 예를 들어 Agent, GenServer, Task는 시작하는 방법을 두 가지 제공해요. start 또는 start_link예요.