이벤트 기반의 병행성
업데이트:
출처
책 “OSTEP”, 33장
이벤트 기반의 병행성
node.js 등에서 쓰이는 이벤트 기반 병행성의 시작점은 C와 유닉스 시스템이다.
멀티 쓰레드 기반 병렬 프로그래밍의 문제
- 어렵다. 교착 상태 등 여러 가지 문제가 발생할 수 있다.
- 개발자가 쓰레드 스케줄링의 제어권을 갖지 않는다. 운영체제가 전부 관리한다.
이벤트 기반 병행성은 위 문제를 해결할 수 있다.
기본 개념 : 이벤트 루프
이벤트의 발생을 대기한다. 이벤트 발생하면 이벤트의 종류를 파악한 후 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 쓰레드 기반으로 할 것인지 시스템의 목적 또는 특성에 맞게 선택해야 한다.
댓글남기기