프로세스

프로세스 (Processes)

Erlang은 대규모 동시성을 위해 설계된 언어예요. 이번 장에서는 프로세스가 어떻게 만들어지고, 이름을 붙이고, 종료되는지, 또 프로세스 사이의 통신이 시그널(signal)을 통해 어떻게 이뤄지는지 살펴볼게요. 링크(link)와 모니터(monitor), 그리고 프로세스 사전까지, Erlang 동시성의 핵심 개념을 하나씩 풀어보겠습니다.

출처: Erlang 공식 문서 - Processes

프로세스 (Processes)

Erlang은 대규모 동시성을 위해 설계됐어요. Erlang 프로세스는 가볍고(동적으로 늘었다 줄었다 하며) 메모리 사용량이 작으며, 생성과 종료가 빠르고 스케줄링 오버헤드가 낮습니다.

프로세스 생성 (Process Creation)

프로세스는 spawn()을 호출해 만들어요:

spawn(Module, Name, Args) -> pid()
  Module = Name = atom()
  Args = [Arg1,...,ArgN]
    ArgI = term()

spawn()은 새 프로세스를 만들고 pid를 반환합니다.

새 프로세스는 Module:Name(Arg1,...,ArgN)에서 실행을 시작하며, 여기서 인자는 (아마 빈) Args 인자 목록의 요소들이에요.

다양한 spawn BIF들이 존재합니다:

등록된 프로세스 (Registered Processes)

pid로 프로세스를 주소 지정하는 것 외에도, 프로세스를 이름 아래에 등록하는 BIF들이 있어요. 이름은 원자여야 하고, 프로세스가 종료되면 자동으로 등록이 해제됩니다:

BIF 설명
register(Name, Pid) 이름 Name(원자)을 프로세스 Pid와 연결합니다.
registered/0 register/2로 등록된 이름들의 목록을 반환해요.
whereis(Name) Name 아래에 등록된 pid를 반환하고, 이름이 등록되지 않았다면 undefined를 반환합니다.

표: 이름 등록 BIF들

프로세스 별칭 (Process Aliases)

프로세스에 메시지를 보낼 때, 수신 프로세스는 Pid, 등록된 이름, 또는 reference 타입의 항인 프로세스 별칭(process alias) 으로 식별할 수 있어요. 프로세스 별칭이 설계된 대표적인 사용 사례는 요청/응답(request/reply) 시나리오예요. 응답을 보낼 때 프로세스 별칭을 사용하면, 연산이 타임아웃되거나 프로세스 사이의 연결이 끊겼을 때 응답 수신자가 응답이 자신의 메시지 큐에 도달하는 것을 막을 수 있어요.

프로세스 별칭은 전송 연산자(!)erlang:send/2 같은 전송 BIF로 메시지를 보낼 때 수신자를 식별하는 데 사용할 수 있어요. 프로세스 별칭이 활성화되어 있는 한, 별칭을 만든 프로세스의 프로세스 식별자를 사용한 것과 똑같은 방식으로 메시지가 전달됩니다. 별칭이 비활성화되면, 별칭으로 보낸 메시지는 수신자의 메시지 큐에 들어가기 전에 버려져요. 비활성화 시점에 이미 메시지 큐에 들어간 메시지는 제거되지 않는다 는 점을 기억해 두세요.

프로세스 별칭은 alias/0,1 BIF 중 하나를 호출하거나, 별칭과 모니터를 동시에 만들어서 생성해요. 별칭을 모니터와 함께 만들면, 같은 reference가 모니터 reference와 별칭 둘 다로 사용됩니다. 모니터와 별칭을 동시에 만드는 것은 monitor/3 BIF에 {alias, _} 옵션을 전달해서 해요. {alias, _} 옵션은 spawn_opt()이나 spawn_request()로 모니터를 만들 때도 전달할 수 있어요.

프로세스 별칭은 그것을 만든 프로세스가 unalias/1 BIF를 호출해 비활성화할 수 있어요. 특정 이벤트에서 별칭을 자동으로 비활성화하는 것도 가능합니다. 별칭 자동 비활성화에 대한 자세한 내용은 alias/1 BIF 문서와 monitor/3 BIF의 {alias, _} 옵션을 참고하세요.

다음은 할 수 없는 일이에요:

  • 호출자 외의 프로세스를 식별하는 별칭을 만들기.
  • 호출자를 식별하지 않는 별칭을 비활성화하기.
  • 별칭을 조회하기.
  • 별칭이 식별하는 프로세스를 조회하기.
  • 별칭이 활성인지 아닌지 확인하기.
  • reference가 별칭인지 확인하기.

이 모든 것은 성능, 확장성, 분산 투명성과 관련된 의도적인 설계 결정이에요.

프로세스 종료 (Process Termination)

프로세스가 종료할 때는 항상 종료 이유(exit reason) 와 함께 종료해요. 이유는 어떤 항이든 될 수 있어요.

종료 이유가 원자 normal이면 프로세스가 정상적으로(normally) 종료한다고 해요. 더 실행할 코드가 없는 프로세스는 정상적으로 종료됩니다.

런타임 오류가 발생하면 프로세스는 종료 이유 {Reason,Stack}으로 종료해요. Exit Reasons를 참고하세요.

프로세스는 다음 BIF 중 하나를 호출해 스스로를 종료할 수 있어요:

그러면 프로세스는 exit/1의 경우 이유 Reason으로, 나머지의 경우 {Reason,Stack}으로 종료됩니다.

프로세스는 normal이 아닌 종료 이유를 가진 종료 시그널을 받으면 종료될 수도 있어요. Error Handling를 참고하세요.

시그널 (Signals)

Erlang 프로세스와 Erlang 포트 사이의 모든 통신은 비동기 시그널을 보내고 받는 방식으로 이뤄져요. 가장 흔한 시그널은 Erlang 메시지 시그널입니다. 메시지 시그널은 전송 연산자 !로 보낼 수 있어요. 받은 메시지는 수신 프로세스가 receive 표현식으로 메시지 큐에서 가져올 수 있습니다.

동기 통신은 여러 개의 비동기 시그널로 분해될 수 있어요. 그런 동기 통신의 예는, 첫 번째 인자가 호출 프로세스의 프로세스 식별자와 같지 않은 erlang:process_info/2 BIF 호출이에요. 호출자는 정보를 요청하는 비동기 시그널을 보내고, 그런 다음 요청한 정보를 담은 응답 시그널을 기다리며 블록됩니다. 요청 시그널이 목적지에 도달하면, 목적지 프로세스가 요청한 정보로 응답해요.

시그널 보내기

프로세스와 포트가 통신하는 데 쓰는 시그널은 많아요. 아래 목록에는 가장 중요한 시그널들이 들어 있습니다. 요청/응답 시그널 쌍의 모든 경우에서, 요청 시그널은 특정 BIF를 호출하는 프로세스가 보내고, 요청된 연산이 수행된 후 응답 시그널이 그 프로세스로 다시 보내져요.

앞서 언급한 clock service, name service, timer service, spawn service는 런타임 시스템이 제공하는 서비스들이에요. 이 서비스들 각각은 독립적으로 실행되는 여러 개체로 구성됩니다. 그런 서비스는 프로세스들의 그룹으로 볼 수 있고, 실제로 그렇게 구현될 수도 있어요. 각 서비스가 여러 독립적으로 실행되는 개체로 구성되기 때문에, 한 서비스에서 한 프로세스로 보내진 여러 시그널 사이의 순서는 보존되지 않습니다. 이는 언어의 시그널 순서 보장을 위반하지 않는다 는 점을 주목하세요.

앞서 설명한 시그널들의 실현 방식은 런타임에서도, 또 구현 변경으로도 바뀔 수 있어요. receive 트레이싱이나 메시지 큐를 검사해서 그런 변화를 감지할 수 있을지도 모릅니다. 하지만 이는 신뢰해서는 안 되는 런타임 시스템의 내부 구현 세부 사항이에요. 예를 들어 많은 응답 시그널은 보통의 메시지 시그널입니다. 연산이 동기식일 때 응답 시그널이 메시지 시그널일 필요는 없어요. 현재 구현은 이를 활용해 시스템 상태에 따라 응답 시그널을 전달하는 대안적인 방식을 씁니다. 이 응답 시그널들의 구현은 언제라도, 이전에 메시지 시그널을 사용했던 곳에서 메시지 시그널을 사용하지 않도록 바뀔 수도 있어요.

시그널 받기

시그널은 비동기적으로, 그리고 자동으로 받아집니다. 프로세스가 시그널 수신을 처리하기 위해 해야 하는 일은 없고, 막을 수도 없어요. 특히 시그널 수신은 receive 표현식의 실행과 묶여 있지 않고, 프로세스의 실행 흐름 어디에서든 일어날 수 있어요.

프로세스가 시그널을 받으면 어떤 종류의 조치가 취해집니다. 취해지는 특정 조치는 시그널 타입, 시그널 내용, 수신 프로세스의 상태에 따라 달라져요. 가장 흔한 시그널들에 대해 취해지는 조치는:

  • message - 메시지 시그널이 더 이상 활성화되지 않은 프로세스 별칭으로 보내졌다면 메시지 시그널은 버려집니다. 그 외에는 별칭이 아직 활성이거나 메시지 시그널이 다른 방식으로 보내졌다면, 메시지가 메시지 큐에 추가돼요. 메시지가 메시지 큐에 추가되면 수신 프로세스는 receive 표현식으로 메시지 큐에서 메시지를 가져올 수 있습니다.

  • link, unlink - 아주 단순화하면, 링크에 대한 프로세스 로컬 정보를 갱신하는 것으로 볼 수 있어요. 링크 프로토콜에 대한 자세한 설명은 ERTS User's GuideDistribution Protocol 장에서 찾을 수 있습니다.

  • exit - 수신자를 종료 상태로 만들거나, 시그널을 버리거나, 시그널을 메시지로 변환해 메시지 큐에 추가해요. 수신자가 종료 상태로 설정되면 더 이상 Erlang 코드가 실행되지 않을 것이고 프로세스는 종료로 스케줄링됩니다. 아래의 Receiving Exit Signals 절에서 exit 시그널을 받았을 때 취해지는 조치에 대한 자세한 내용을 볼 수 있어요.

  • monitor, demonitor - 모니터에 대한 프로세스 로컬 정보를 갱신합니다.

  • down, change - 해당 monitor가 여전히 활성이면 메시지로 변환되고, 그렇지 않으면 시그널이 버려져요. 시그널이 메시지로 변환되면 메시지 큐에도 추가됩니다.

  • group_leader - 프로세스의 그룹 리더를 바꿉니다.

  • spawn_reply - 응답과 spawn_request 시그널이 구성된 방식에 따라 메시지로 변환하거나 시그널을 버려요. 시그널이 메시지로 변환되면 메시지 큐에도 추가됩니다. 자세한 내용은 spawn_request() BIF를 참고하세요.

  • alive_request - is alive 테스트의 실행을 스케줄링합니다. 프로세스가 종료 상태라면, 프로세스가 쓰던 모든 직접 보이는 Erlang 리소스가 해제될 때까지 is alive 테스트는 실행되지 않을 거예요. alive_replyis alive 테스트가 실행된 뒤에 보내집니다.

  • process_info_request, garbage_collect_request, check_process_code_request - 요청된 연산의 실행을 스케줄링합니다. 응답 시그널은 연산이 실행됐을 때 보내져요.

시그널을 받았을 때 취해지는 조작 중 일부는 스케줄링 이후의 조작을 포함하며, 그 스케줄링된 조작이 끝나면 응답 시그널이 생깁니다. 이는 응답 시그널이 그 연산들을 촉발한 수신 시그널의 순서와 다른 순서로 보내질 수 있음을 의미해요. 하지만 이것은 언어의 시그널 순서 보장을 위반하지는 않습니다.

메시지 큐에 메시지 추가하기

메시지 시그널이 받아지면 취해지는 조치는 메시지를 메시지 큐에 추가하는 것이에요. 다른 시그널들이 받아졌을 때 취해지는 조작도 메시지를 메시지 큐에 추가할 수 있어요(위 참조). 수신 프로세스가 우선 메시지를 활성화하지 않았다면, 모든 메시지는 메시지 큐의 끝에 추가됩니다. 수신자가 우선 메시지를 활성화하지 않은 경우, 메시지 큐에 있는 메시지들의 순서는 큐에 있는 메시지들에 대응하는 시그널들이 받아진 순서를 반영할 거예요. 같은 발신자의 시그널에 대응하는 메시지들은 언어의 시그널 순서 보장 덕에, 시그널들이 보내진 순서와 같은 순서로 정렬되기도 해요.

OTP 28.0부터 프로세스는 우선 메시지 수신을 활성화할 수 있어요. 수신 프로세스가 우선 메시지를 활성화했다면, 수신자는 메시지 시그널이나 메시지로 변환되는 다른 시그널을 받았을 때 그것을 우선 메시지로 받아들일지 아닐지 검사합니다. 우선 메시지로 받아들여지면 큐에서 마지막으로 받아들여진 우선 메시지의 뒤에 추가되고, 그렇지 않으면 보통 메시지로 취급되어 메시지 큐의 끝에 추가돼요.

우선 메시지 수신

그림 1.

우선 메시지로 받아들여진 메시지들이 메시지 큐에 존재하면, 메시지 큐의 메시지 순서는 큐에 있는 메시지들에 대응하는 시그널들이 받아진 순서를 반영하지 않을 거예요. 하지만 우선 메시지의 순서는 그 대응 시그널들이 받아진 순서에 대응하고, 보통 메시지의 순서도 그 대응 시그널들이 받아진 순서에 대응해요. 따라서 같은 발신자가 보낸 두 메시지가 보내진 순서와 다른 순서로 있을 수 있습니다.

우선 메시지는 언어의 시그널 순서 보장을 위반하지 않는다 는 점을 주목하세요. 시그널은 여전히 수신 프로세스에 같은 순서로 전달됩니다. 유일한 차이는 수신 프로세스가 시그널을 받은 후 메시지가 메시지 큐에 추가되는 방식뿐이에요.

receive 표현식은 메시지 큐에서 처음부터, 마치 보통 메시지만 메시지 큐에 있는 것처럼, 매칭되는 첫 번째 메시지를 선택합니다. 그림 1의 총 메시지 큐 길이는 P+M과 같아요. 길이 PM은 보이지 않고, 총 메시지 큐 길이만 결정할 수 있습니다. Erlang 코드가 메시지 큐에서 메시지를 가져올 때 우선 메시지와 보통 메시지를 구분할 방법은 없어요. 그런 지식은 프로세스가 따라야 할 메시지 프로토콜의 일부여야 합니다.

우선 메시지 수신 활성화하기

경고 {: .warning }

우선 메시지는 이전에 보통 시그널링으로는 그런 문제를 효율적으로 풀기 매우 어려웠던 아주 특정한 문제들을 풀기 위한 것입니다. 우선 메시지를 써야 할 일은 아주 드물 거예요. 수신 프로세스는 대량의 우선 메시지를 처리하도록 최적화되어 있지 않습니다. 프로세스가 대량의 우선 메시지를 쌓는다면, 그 메시지 프로토콜의 설계 자체를 다시 설계해야 해요. 우선 메시지는 그렇게 쓰라고 만들어진 게 아니거든요.

프로세스는 우선 프로세스 별칭(priority process alias) 또는 짧게 우선 별칭(priority alias) 을 만들어서 우선 메시지 수신을 활성화할 수 있어요. 그런 별칭은 erlang:alias/1 BIF를 priority 옵션으로 호출해서 만들어요. 이 우선 별칭은 별칭을 만든 프로세스에 우선 메시지를 보낼 수 있어야 하는 프로세스들에게 배포되어야 합니다. 우선 메시지를 보내려면, priority 옵션을 옵션 목록에 전달함과 동시에 우선 별칭을 erlang:send/3 BIF에 전달해야 해요. 메시지가 우선 메시지로 받아들여지려면 우선 별칭과 priority 옵션 모두가 전달되어야 한다는 점을 주목하세요.

우선 별칭은 종료 시그널을 보내는 데도 쓸 수 있는데, 수신자가 exit를 트랩하면 그것들은 우선 메시지로 처리돼요. 이 경우 우선 별칭을 erlang:exit/3 BIF의 첫 번째 인자로 전달하고 priority 옵션을 옵션 목록에 전달해야 합니다. 이 경우에도 메시지가 우선 메시지로 받아들여지려면 우선 별칭과 priority 옵션 모두 전달되어야 해요. 이는 수신자가 exit를 트랩할 때 잠재적인 exit 메시지가 처리되는 방식에만 영향을 준다는 점을 주목하세요. exit 시그널 자체의 우선순위가 높아지지는 않아요.

우선 별칭의 생성자가 별칭을 비활성화하면, 별칭으로 보낸 시그널은 프로세스 별칭의 경우와 똑같이 버려집니다.

우선 메시지 수신은 끊어진 링크로 인한 종료 시그널과 모니터로 촉발된 메시지에 대해서도 활성화할 수 있어요. 이런 시그널은 프로세스가 시그널을 보내는 특정 함수를 호출할 때 보내지는 게 아니라 시스템에서 특정 이벤트가 발생할 때 보내지므로, 우선 별칭은 여기에 쓸 수 없습니다.

모니터로 촉발된 메시지의 우선 메시지 수신을 활성화하려면, 모니터를 만드는 프로세스가 erlang:monitor/3 BIF로 모니터를 만들면서 priority 옵션을 전달해야 해요. 그러면 모니터가 촉발되어 받은 메시지가 우선 메시지로 처리됩니다.

끊어진 링크로 인한 종료 시그널의 우선 메시지 수신을 활성화하려면, exit 시그널을 우선 메시지로 원하는 프로세스가 erlang:link/2 BIF를 priority 옵션으로 호출해야 해요. priority 옵션은 priority 옵션으로 erlang:link/2 BIF를 호출한 프로세스에 대해서만 종료 시그널의 우선 메시지 처리를 활성화해요. 이 경우에도 수신자가 exit를 트랩할 때 잠재적인 exit 메시지가 처리되는 방식에만 영향을 줍니다.

직접 보이는 Erlang 리소스

앞서 설명했듯이 링크로 인한 exit 시그널, down 시그널, alive_request로 인한 종료 프로세스의 응답 시그널은 종료 프로세스가 보유한 모든 직접 보이는 Erlang 리소스(directly visible Erlang resources) 가 해제될 때까지 보내지지 않아요. 직접 보이는 Erlang 리소스 란 여기서 언어가 제공하는 모든 리소스에서 힙 데이터가 보유한 리소스, 더티 네이티브 코드 실행, 종료 프로세스의 프로세스 식별자를 제외한 것을 의미해요. 직접 보이는 Erlang 리소스 의 예로는 등록된 이름ETS 테이블이 있어요.

제외되는 리소스

프로세스의 프로세스 식별자는 프로세스에 관한 모든 것이 해제될 때까지 재사용을 위해 해제될 수 없어요.

NIF에서 더티 네이티브 코드를 실행 중인 프로세스가 exit 시그널을 받으면, 여전히 더티 네이티브 코드를 실행 중이더라도 종료 상태로 설정돼요. 직접 보이는 Erlang 리소스 는 해제되지만, 런타임 시스템은 네이티브 코드가 실행을 멈추도록 강제할 수 없어요. 런타임 시스템은 종료된 프로세스에서 사용될 때 enif_send() 같은 기능을 비활성화하는 방식으로 더티 네이티브 코드의 실행이 다른 프로세스에 영향을 주는 것을 막으려 하지만, NIF가 잘 behaved 하지 않으면 여전히 다른 프로세스에 영향을 줄 수 있어요. 잘 behaved 한 더티 NIF는 자신이 실행 중인 프로세스가 종료했는지 검사하고, 종료했다면 실행을 멈춰야 합니다.

일반적으로 프로세스의 힙은 그것이 보내야 하는 모든 시그널이 보내지기 전에는 제거될 수 없어요. 힙 데이터가 보유한 리소스는 힙을 담은 메모리 블록뿐 아니라, off-heap 바이너리처럼 힙에서 참조되는 것들, 힙 위의 NIF 리소스 객체로 보유된 리소스들도 포함합니다.

시그널의 전달

시그널이 보내진 시간과 시그널이 목적지에 도착하는 시간 사이에 경과하는 시간은 지정되지 않았지만 양수예요. 수신자가 종료했다면 시그널은 도착하지 않지만, 다른 시그널을 촉발할 수 있어요. 예를 들어 존재하지 않는 프로세스로 보내진 link 시그널은 exit 시그널을 촉발하고, 그 시그널은 link 시그널이 시작된 곳으로 다시 보내져요. 분산(distribution)을 통해 통신할 때는 분산 채널이 다운되면 시그널을 잃을 수 있어요.

주어지는 유일한 시그널 순서 보장은 다음과 같아요: 어떤 개체가 같은 목적지 개체에 여러 시그널을 보내면 순서가 보존됩니다. 즉 AB에 시그널 S1을 보내고 나중에 B에 시그널 S2를 보내면, S1S2 다음에 도착하지 않는 것이 보장됩니다. S1이 손실됐을 수도 있고 아닐 수도 있다는 점을 주목하세요.

불규칙성 (Irregularities)

  • 동기 오류 검사 - 시그널을 보내는 일부 기능은 노드에서 로컬로 보낼 때 동기 오류 검사를 하며, 시그널을 보낼 때 수신자가 없다면 실패해요:

  • exit 시그널의 예상 밖의 동작 - 종료 이유가 killexit 시그널을 받으면, 취해지는 조치는 시그널이 링크된 프로세스의 종료 때문에 보내졌는지, 아니면 exit_signal/2 BIF로 명시적으로 보내졌는지에 따라 달라져요. exit_signal/2 BIF로 보내졌다면 그 시그널은 트랩될 수 없지만, 링크 때문에 보내진 시그널은 트랩될 수 있어요.

  • 분산을 통한 블로킹 시그널링 분산 채널을 통해 시그널을 보낼 때, 시그널이 비동기적으로 보내져야 하는데도 보내는 프로세스가 일시 중단될 수 있어요. 이것은 거의 항상 존재해 온 채널의 내장 흐름 제어 때문이에요. 채널의 출력 버퍼 크기가 분산 버퍼 사용 한계(distribution buffer busy limit) 에 도달하면, 채널에 보내는 프로세스들은 버퍼 크기가 그 한계 아래로 줄어들 때까지 일시 중단됩니다.

    버퍼가 가득 찬 이유에 따라, 일시 중단된 프로세스가 재개되기까지 걸리는 시간은 매우 달라질 수 있어요. 그 결과의 한 예로, erpc:call() 호출의 타임아웃이 상당히 지연될 수 있어요.

    이 기능은 너무 오래 존재해서 제거할 수 없지만, process_flag(async_dist, Bool)로 프로세스 단위로 완전 비동기 분산 시그널링 을 활성화할 수 있어요. 이는 블로킹 시그널링 때문에 발생하는 문제를 푸는 데 쓸 수 있습니다. 다만 완전 비동기 분산 시그널링 으로 보내진 데이터에 대한 흐름 제어가 구현되어 있거나, 그런 데이터의 양이 항상 제한적이라고 알려져 있는지 확인해야 해요. 그렇지 않으면 과도한 메모리 사용이 발생하는 상황에 빠질 수 있습니다.

    분산 버퍼 사용 한계 의 크기는 erlang:system_info(dist_buf_busy_limit)를 호출해 검사할 수 있어요.

앞서 언급한 불규칙성들은 Erlang에 너무 오래 있어서 기존 코드를 많이 깨뜨리기 때문에 고칠 수 없어요.

두 프로세스는 서로 링크 될 수 있어요. 또한 같은 노드에 있는 프로세스와 포트도 서로 링크될 수 있습니다. 두 프로세스 사이의 링크는 그중 하나가 다른 프로세스의 프로세스 식별자를 인자로 link/1 BIF를 호출하면 만들어질 수 있어요. 링크는 다음 spawn BIF 중 하나로도 만들 수 있습니다: spawn_link(), spawn_opt(), spawn_request(). 이 경우 spawn 연산과 link 연산은 원자적으로 수행돼요.

링크의 참여자 중 하나가 종료하면, 다른 참여자에게 exit 시그널을 보냅니다. exit 시그널은 종료한 참여자의 종료 이유를 담고 있어요.

링크는 unlink/1 BIF를 호출해 제거할 수 있습니다.

링크는 양방향이고, 두 프로세스 사이에는 링크가 하나만 있을 수 있어요. link()를 반복 호출해도 효과가 없습니다. 관련된 프로세스 중 어느 쪽이든 링크를 만들거나 제거할 수 있어요.

링크는 다른 프로세스의 동작을 모니터링하는 데 사용됩니다. Error Handling를 참고하세요.

오류 처리 (Error Handling)

Erlang에는 프로세스 사이의 오류 처리를 위한 내장 기능이 있어요. 종료하는 프로세스는 모든 링크된 프로세스에게 exit 시그널을 발산하며, 링크된 프로세스는 같이 종료하거나 어떤 방식으로든 exit을 처리할 수 있습니다. 이 기능은 어떤 프로세스들이 다른 프로세스들을 감독하는 계층적 프로그램 구조를 만드는 데 쓰일 수 있어요. 예를 들어 프로세스가 비정상적으로 종료하면 그것들을 다시 시작하는 방식처럼요.

이 기능을 사용하는 OTP 감독 트리(supervision tree)에 대한 자세한 내용은 OTP Design Principles을 참고하세요.

exit 시그널 보내기

프로세스나 포트가 종료하면, 링크된 모든 프로세스와 포트에 exit 시그널을 보냅니다. exit 시그널은 다음 정보를 담아요:

  • 발신자 식별자 - 종료한 프로세스 또는 포트의 프로세스/포트 식별자.

  • 수신자 식별자 - exit 시그널이 보내지는 프로세스 또는 포트의 프로세스/포트 식별자.

  • link 플래그 - 이 플래그는 exit 시그널이 링크 때문에 보내졌음을 나타내도록 설정되요.

  • Exit reason 종료한 프로세스 또는 포트의 종료 이유 또는 원자:

    • noproc - 이전의 link(PidOrPort) BIF 호출에서 링크를 설정할 때 프로세스나 포트를 찾지 못한 경우. exit 시그널의 발신자로 식별되는 프로세스 또는 포트는 link/1에 전달된 PidOrPort 인자와 같을 거예요.
    • noconnection - 링크된 프로세스들이 서로 다른 노드에 있고 노드 사이의 연결이 끊어졌거나 수립될 수 없는 경우. 이 경우 exit 시그널의 발신자로 식별되는 프로세스나 포트는 여전히 살아 있을 수 있어요.

exit 시그널은 exit_signal(PidOrPort, Reason) BIF를 호출해 명시적으로 보낼 수도 있어요. exit 시그널은 PidOrPort 인자가 식별하는 프로세스나 포트로 보내집니다. 보내진 exit 시그널은 다음 정보를 담아요:

  • 발신자 식별자 - exit_signal/2를 호출한 프로세스의 프로세스 식별자.

  • 수신자 식별자 - exit 시그널이 보내지는 프로세스 또는 포트의 프로세스/포트 식별자.

  • link 플래그 - 이 플래그는 설정되지 않으며, 이 exit 시그널이 링크 때문에 보내진 것이 아님을 나타내요.

  • Exit reason - exit_signal/2 호출에서 Reason으로 전달된 항. Reason이 원자 kill이라면 수신자는 exit을 트랩할 수 없고, 시그널을 받으면 무조건 종료됩니다.

exit 시그널 받기

프로세스가 exit 시그널을 받았을 때 일어나는 일은 다음에 달려 있어요:

  • exit 시그널을 받은 시점의 수신자의 trap exit 상태.
  • exit 시그널의 종료 이유.
  • exit 시그널의 발신자.
  • exit 시그널의 link 플래그 상태. link 플래그가 설정되어 있으면 exit 시그널은 링크 때문에 보내진 것이고, 그렇지 않으면 exit_signal/2 BIF 호출로 보내진 거예요.
  • link 플래그가 설정되어 있다면, exit 시그널을 받았을 때 링크가 여전히 활성인지 아닌지에 따라서도 달라져요.

위 상태들에 기반해, 프로세스가 exit 시그널을 받으면 다음이 일어납니다:

  • 다음 경우에는 exit 시그널이 조용히 버려져요:

    • exit 시그널의 link 플래그가 설정되어 있고 해당 링크가 비활성화되었을 때.
    • exit 시그널의 종료 이유가 원자 normal이고 수신자가 exit를 트랩하지 않을 때.
  • 다음 경우에는 수신 프로세스가 종료됩니다:

    • exit 시그널의 link 플래그가 설정되지 않았고 exit 시그널의 종료 이유가 원자 kill일 때. 수신 프로세스는 종료 이유 killed로 종료해요.
    • 수신자가 exit를 트랩하지 않고 종료 이유가 원자 normal이 아닐 때. 또한 exit 시그널의 link 플래그가 설정되어 있다면 링크도 활성이어야 하며, 그렇지 않으면 exit 시그널이 버려져요. 수신 프로세스의 종료 이유는 exit 시그널의 종료 이유와 같아요. link 플래그가 설정된 경우 종료 이유 killkilled로 변환되지 않을 것이라는 점을 주목하세요.
  • 다음 경우에는 exit 시그널이 메시지 시그널로 변환되어 수신자의 메시지 큐 끝에 추가됩니다. 수신자가 exit를 트랩하고 있고, exit 시그널의 link 플래그가:

    • 설정되지 않았고 시그널의 종료 이유가 원자 kill이 아닐 때.
    • 설정되어 있고 해당 링크가 활성일 때. 이 경우 종료 이유 kill이 프로세스를 종료시키지 않고 killed로 변환되지도 않는다는 점을 주목하세요.

    변환된 메시지는 {'EXIT', SenderID, Reason} 형태이며, 여기서 Reason은 exit 시그널의 종료 이유와 같고 SenderID는 exit 시그널을 보낸 프로세스 또는 포트의 식별자예요.

모니터 (Monitors)

링크의 대안은 모니터(monitor) 예요. 프로세스 Pid1은 BIF erlang:monitor(process, Pid2)를 호출해 Pid2에 대한 모니터를 만들 수 있어요. 이 함수는 reference Ref를 반환합니다.

Pid2가 종료 이유 Reason으로 종료하면, 'DOWN' 메시지가 Pid1로 보내져요:

{'DOWN', Ref, process, Pid2, Reason}

Pid2가 존재하지 않으면, 'DOWN' 메시지가 즉시 Reasonnoproc으로 설정되어 보내집니다.

모니터는 단방향이에요. erlang:monitor(process, Pid)를 반복 호출하면 여러 독립적인 모니터가 만들어지고, 각각이 Pid가 종료할 때 'DOWN' 메시지를 보내요.

모니터는 erlang:demonitor(Ref)를 호출해 제거할 수 있어요.

모니터는 등록된 이름을 가진 프로세스에 대해, 다른 노드에서도 만들 수 있습니다.

프로세스 사전 (Process Dictionary)

각 프로세스는 자신만의 프로세스 사전을 가지며, 다음 BIF들을 호출해 접근해요:

더 알아보기 (Learn more)