Elixir에서는 모든 코드가 프로세스 안에서 실행돼요.
함수는 기본적으로 자신을 호출한 프로세스에서 실행돼요. 특정 함수를 새로운 프로세스에서 명시적으로 실행해야 할 때는 spawn/1을 사용해요.
spawn(fn -> 2 + 2 end)
# => #PID<0.125.0>
spawn/1은 주어진 인자가 0개인 함수를 실행하는 새로운 프로세스를 만들고, 프로세스 식별자(PID)를 반환해요. 새 프로세스는 함수가 실행되는 동안 살아 있다가 조용히 종료돼요.
Elixir의 프로세스를 운영체제 프로세스와 혼동하면 안 돼요. Elixir의 프로세스는 메모리와 CPU를 훨씬 적게 사용해요. Elixir 애플리케이션이 수백 개의 Elixir 프로세스를 실행해도 전혀 문제없어요.
프로세스는 서로 정보를 직접 공유하지 않아요. 프로세스는 데이터를 공유하기 위해 메시지를 주고받아요.
send/2를 사용하면 어떤 프로세스에든 메시지를 보낼 수 있어요. send/2의 첫 번째 인자는 받는 프로세스의 PID이고, 두 번째 인자는 메시지예요.
메시지는 어떤 타입이든 될 수 있어요. 흔히 아톰과 튜플로 이루어져 있어요. 응답을 받고 싶다면 메시지 어딘가에 보내는 프로세스의 PID를 넣어야 해요. 현재 프로세스의 PID는 self()로 얻을 수 있어요.
send/2는 메시지가 수신자에게 전달되었는지, 수신자가 아직 살아 있는지 확인하지 않아요. 메시지는 수신자의 _메일박스_에 들어가고, 수신자가 명시적으로 메시지 수신을 요청할 때에만 읽혀요.
메시지는 receive/1 매크로를 사용해 메일박스에서 읽을 수 있어요. receive/1은 메시지를 패턴 매칭할 수 있는 do 블록을 받아요.
receive do
{:ping, sender_pid} -> send(sender_pid, :pong)
:do_nothing -> nil
end
receive/1은 주어진 패턴 중 하나에 일치하는 _메시지 하나_를 메일박스에서 꺼내서, 그 패턴에 해당하는 표현식을 실행해요. 메일박스에 메시지가 없거나, 메일박스의 어떤 메시지도 주어진 패턴에 일치하지 않으면, receive/1은 메시지 하나를 기다려요.
메시지를 두 개 이상 받고 싶다면 receive/1을 재귀적으로 호출해야 해요. receive/1을 호출하고, 메시지로 무언가를 한 다음, 다시 자신을 호출해 더 많은 메시지를 기다리는 재귀 함수를 만드는 것이 흔한 패턴이에요. 예를 들어 그 함수의 이름을 loop라고 지을 수 있어요. 한 receive/1 호출에서 다음 호출로 어떤 상태를 넘겨야 한다면, 그 loop 함수에 인자를 전달하면 돼요.
프로세스 식별자는 그 자체로 하나의 데이터 타입이에요. 프로세스 식별자는 메일박스 주소 역할을 해요. 어떤 프로세스의 PID를 알고 있다면 그 프로세스에 메시지를 보낼 수 있어요. PID는 보통 spawn처럼 새로운 프로세스를 만드는 함수의 반환 값으로 간접적으로 만들어져요.
번호표 발권기를 위한 임베디드 시스템을 작성하고 있어요. 아주 단순한 모델이에요. 이 기계는 연속된 번호를 내주고, 마지막으로 내준 번호가 무엇이었는지 알려줄 수 있어요.
start/0 함수를 구현해요. 이 함수는 새 프로세스를 생성하고 그 프로세스의 PID를 반환해야 해요. 새 프로세스는 아직 아무것도 하지 않아도 돼요.
TakeANumber.start()
# => #PID<0.138.0>
이 코드를 실행할 때마다 PID는 다를 수 있어요.
새로 생성된 프로세스가 초기 상태 0으로 메시지를 받을 준비를 하도록, 즉 receive 루프를 시작하도록 기계를 수정해요. 이 프로세스는 {:report_state, sender_pid} 메시지를 받을 수 있어야 해요. 그런 메시지에 대한 응답으로 자신의 현재 상태(마지막으로 내준 번호)를 sender_pid에게 보내고, 그런 다음 다시 메시지를 기다려요.
machine_pid = TakeANumber.start()
# a client sending a message to the machine
send(machine_pid, {:report_state, self()})
# a client receiving a message from the machine
receive do
msg -> msg
end
# => 0
{:take_a_number, sender_pid} 메시지를 받을 수 있도록 기계를 수정해요. 상태를 1만큼 증가시키고, 새로운 상태를 sender_pid에게 보낸 다음, 다시 메시지를 기다려요.
machine_pid = TakeANumber.start()
# a client sending a message to the machine
send(machine_pid, {:take_a_number, self()})
# a client receiving a message from the machine
receive do
msg -> msg
end
# => 1
:stop 메시지를 받을 수 있도록 기계를 수정해요. 그러면 더 이상 메시지를 기다리지 않아요.
예상치 못한 메시지를 받았을 때, 그 메시지를 무시하고 계속해서 다음 메시지를 기다리도록 기계를 수정해요.
Exercism에 가입하고 Elixir 트랙을 개념 58개연습 문제 168개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.