Elixir에서는 모든 코드가 프로세스 안에서 실행돼요. Elixir의 프로세스는:
기본적으로 함수는 자신을 호출한 프로세스에서 실행돼요. 특정 함수를 새로운 프로세스에서 명시적으로 실행해야 할 때는 spawn을 사용해요:
spawn/1은 직접 실행할 함수를 받아요.
spawn(fn -> 2 + 2 end)
# => #PID<0.125.0>
spawn/3은 모듈 이름, 함수 이름(atom), 그리고 그 함수에 전달할 인자 목록을 받아 실행해요.
spawn(String, :split, ["hello there", " "])
# => #PID<0.113.0>
프로세스는 함수의 실행이 끝나는 즉시 종료돼요.
프로세스가 아직 살아 있는지(실행 중인지)는 Process.alive?/1으로 확인할 수 있어요:
pid = spawn(fn -> 2 + 2 end)
Process.alive?(pid)
# => false
프로세스는 서로 정보를 직접 공유하지 않아요. 프로세스는 데이터를 공유하기 위해 메시지를 보내요. 이런 동시성 패턴을 액터 모델이라고 해요.
프로세스에 메시지를 보낼 때는 send/2를 사용해요.
send(pid, :hello)
send는 메시지가 잘 도착했는지도, 수신자가 아직 살아 있는지도 확인하지 않아요.메시지는 어떤 타입이든 될 수 있어요.
현재 프로세스로 보내진 메시지는 receive/1으로 받을 수 있어요.
receive는 주어진 패턴 중 하나라도 일치하는 메시지가 프로세스의 메일박스에 들어올 때까지 기다려요.
after 블록으로 타임아웃을 줄 수 있어요.receive/1에는 항상 모든 경우를 처리하는 _ 절을 써요.receive do
{:ping, sender_pid} -> send(sender_pid, :pong)
_ -> nil
after
5000 ->
{:error, "No message in 5 seconds"}
end
메시지를 두 개 이상 받고 싶다면 receive/1을 재귀적으로 호출해야 해요. 흔한 패턴은 receive/1을 호출하고, 메시지로 무언가를 처리한 뒤, 다시 자신을 호출해 다음 메시지를 기다리는 재귀 함수를 구현하는 거예요. 이런 함수에는 보통 loop라는 이름을 붙여요. receive/1 호출 사이에 어떤 상태를 이어가야 한다면, 그 loop 함수에 인자를 넘겨서 할 수 있어요.
def loop(state) do
receive do
:increment_by_one ->
loop(state + 1)
{:report_state, sender_pid} ->
send(sender_pid, state)
loop(state)
:stop ->
nil
_ ->
loop(state)
end
end
실제로는 이런 방식을 직접 쓰는 경우가 드물어요. Elixir는 수신 루프를 기반으로 만들어지는 동시성 추상화를 제공해요. 예를 들어 Agent 모듈이나 GenServer behaviour가 있어요. 그렇지만 이런 추상화를 효율적으로 사용하려면 그 기초를 이해하는 것이 아주 중요해요.