이벤트 기반의 병행성

업데이트:

출처

책 “OSTEP”, 33장

이벤트 기반의 병행성

node.js 등에서 쓰이는 이벤트 기반 병행성의 시작점은 C와 유닉스 시스템이다.

멀티 쓰레드 기반 병렬 프로그래밍의 문제

  1. 어렵다. 교착 상태 등 여러 가지 문제가 발생할 수 있다.
  2. 개발자가 쓰레드 스케줄링의 제어권을 갖지 않는다. 운영체제가 전부 관리한다.

이벤트 기반 병행성은 위 문제를 해결할 수 있다.

기본 개념 : 이벤트 루프

이벤트의 발생을 대기한다. 이벤트 발생하면 이벤트의 종류를 파악한 후 I/O를 요청하거나 다른 이벤트를 발생시키는 등의 작업을 한다.

while (1) {
	events = getEvents();
	for (e in events)
		processEvent(e);
}

이벤트를 처리하는 코드를 이벤트 핸들러라 한다. 다음에 처리할 이벤트를 결정하는 것이 스케줄링과 동일한 효과를 갖는다. 스케줄링을 제어할 수 있다!

중요 API : select() (or poll())

I/O 이벤트 발생 시 처리할 게 있는 지 검사하는 시스템 콜.

int select(int nfds,
		  fd_set *restrict readfds,
		  fd_set *restrict writefds,
		  fd_set *restrict errorfds,
		  struct timeval *restrict timeout);

파일 디스크립터에 대한 읽기 또는 쓰기 가능여부를 파악할 수 있다. timeout만큼 대기한다.

락이 필요 없음

한번에 하나의 이벤트만을 처리하기 때문에 락을 획득하거나 해제할 필요가 없다.

문제점 : 블로킹 시스템 콜

이벤트 기반의 접근법은 쓰레드가 없고 이벤트 루프만 존재한다.

  • 이벤트 핸들러가 블로킹 콜을 호출하면 서버 전체가 정지.
  • 다른 요청들의 처리가 중단.
  • 심각한 자원 낭비가 발생.

해법 : 비동기 I/O

현대 운영체제들은 디스크 입출력 요청 방식 중 하나로 비동기 I/O를 개발했다.

비동기 콜은 읽기 작업의 완료를 대기하지 않고 즉시 리턴한다. 응용 프로그램은 블로킹 없이 계속 진행한다. 그렇다면 I/O의 완료는 어떻게 파악할까? 별도 시스템 콜이 존재한다. 위 비동기 읽기 시스템 콜에 의해 참조된 요청이 완료되었는지 검사한다.

그러나, I/O 완료 여부를 계속해서 검사(폴링) 하는 것은 성가신 일이다.

어떤 시스템들은 인터럽트 기반의 접근법을 제공한다. 유닉스 signal을 사용해 완료 여부를 알려주기에 폴링할 필요가 없다.

문제점 : 상태 관리

이벤트 핸들러가 비동기 I/O를 발생시킬 때 I/O 완료 시 사용할 프로세스 상태를 관리해야 한다.

  • 수동 스택 관리 라 한다. 쓰레드 기반은 이 작업이 필요없다. 스택에 상태 정보가 들어 있다.

해법은 continuation 개념을 사용하는 것이다.

콜백 패턴처럼 다음에 실행할 작업을 등록해두는 개념인 듯.

이벤트 사용의 어려움

멀티 CPU의 경우에는 복잡해진다.

  • 다수의 CPU 활용을 위해 다수의 이벤트 핸들러를 병렬 실행한다.
  • 임계 영역 보호 등 동기화 문제가 발생. 락을 사용할 수 밖에.
  • 멀티코어 시스템에서는 락 없는 이벤트 처리 방식을 사용할 수 없다.

운영체제 동작과 맞지 않을 수 있다.

  • 이벤트 핸들러에서 페이지 폴트가 발생하면, 프로세스는 블락된다.
  • 프로세스가 논블락킹이라도, 운영체제 레벨까지 대비하기는 어렵다.

루틴 동작 방식의 변화에 민감하다.

  • 루틴 동작이 논블락에서 블락킹으로 변경되었다면 이벤트 핸들러도 적절하게 수정되어야 한다.

비동기 I/O를 지원해야 한다.

  • 현재 대부분의 운영체제가 지원한다.
  • 다만 간단하고 일관성 있게 적용되어 있지는 않다.

요약

병렬 프로그램을 작성할 때는 이벤트 기반 or 쓰레드 기반으로 할 것인지 시스템의 목적 또는 특성에 맞게 선택해야 한다.

댓글남기기