동기와 비동기
컴퓨터 시스템은 다양한 작업을 처리한다. 그 과정에서는 “기다리는가?” 혹은 “기다리지 않는가?”의 선택을 자주 마주친다. 이 선택은 동기(Synchronous)와 비동기(Asynchronous)라는 방식으로 이어진다.
동기와 비동기의 개념은 단순한 프로그래밍 기법이 아니라, 운영체제의 깊은 설계 철학과도 연결되어 있다. 이 글에서는 운영체제 관점에서 이 둘의 차이를 이해하고, 왜 비동기가 현대 시스템에서 점점 더 중요해지는지 살펴보자
정의
동기 (Synchronous)
동기 방식은, 작업 요청 이후 결과가 올 때 까지 기다리는 방식을 의미한다. 작업 요청자는 응답이 올 때 까지 다음 작업을 진행하지 않고 기다린다. 즉, 응답이 없다면 다음 작업을 진행할 수 없다.
관련 예를 들면, 우리가 은행에서는 은행 직원에게 요청을 하고, 직원의 응답 결과에 따라 우리는 일을 진행하게 된다. 만약 기다리지 않고 관련된 업무를 진행하려고 해도, 진행할 수가 없다.
비동기 (Asynchronous)
비동기 방식은, 작업 요청만 하고 결과는 나중에 받는 형식이다. 작업 요청자는 응답과 무관하게 다음 작업을 바로 시작한다는 것이다.
비유하자면 요리와도 유사하다. 우리가 라면을 끓일 때, 물을 끓이고 끓고 나서 스프, 면을 넣지만, 물이 다 끓어야 라면에서 스프를 꺼내고 면을 꺼내고 넣고 기다리고 하지 않는다 (대부분..?). 물을 끓일 동안 우리는 면과 스프, 파 등등을 준비해 놓는다.
블로킹과 논블로킹
블로킹과 논블로킹. 말 그대로 “막거나”, “막지 않거나” 이다. 내가 하던 작업을 막아 멈추게 하거나, 내가 하던 작업을 막지 않고 즉시 반환하는 것이다.
구분 | 설명 | sync? |
---|---|---|
블로킹 | 결과가 올 때까지 실제로 멈춤 | 동기 |
논블로킹 | 즉시 반환, 다만 결과가 없을 수도 있음 | 비동기 |
블로킹 = 동기, 논블로킹 = 비동기?
항상 그렇지는 않는다. 예를 들어
select()
시스템 콜은 비동기적 동작을 동기식 호출로 처리할 수 있다.
운영체제에서의 사례
동기와 비동기는, 운영체제에서 자주 사용되는데, 그 예제는 다음과 같다.
I/O 처리
시스템 콜
read()
,write()
등은 전통적으로 동기 호출이다.- 최근에는
aio_read()
,epoll()
같은 비동기 시스템 콜도 제공된다.
동기 시 문제
동기는 결과가 올 때 까지 기다리는 형식을 보인다. 그렇기 때문에 앞선 요청이 오래 걸리는 작업이라면, 전반적인 프로그램 속도가 매우 늦어질 것이다. 아래의 문제가 이 상황으로 발생된다.
- 입출력 시간이 긴 작업이 전체 시스템 응답을 늦춘다.
- 자원 대기가 길어지면 병목 현상 발생
- 멀티태스킹 환경에서 비효율적 자원 사용
그렇다면 왜 비동기를 사용하는가?
그렇기 때문에 비동기를 사용하게 된다. 기다리지 않아도 되고, 응답도 빠르고.
- 병렬성 확보: 하나의 자원이 대기 중일 때도 다른 작업 가능
- 시스템 확장성 향상: 고부하 상황에서도 처리량 유지
- 응답성 개선: 사용자 요청에 빠르게 반응 가능
- 비용 절감: 스레드/프로세스 수 최소화로 메모리 효율 향상
비동기의 문제
지금 보면 동기 보다는 비동기로 사용하는 것이 조금 더 효율적인 것 처럼 보인다. 하지만, 비동기 또한 문제점을 발생하는데, 아래의 문제가 비동기에서 발생한다.
- 상태 추척이 어려움
- 디버깅이 어렵고 복잡도가 증가한다.
- 순서를 보장하기 어렵다
- CPU 연산에는 오히려 느릴 수 있다.
요약
구분 | 동기 | 비동기 |
---|---|---|
처리 방식 | 순차 처리 | 병렬 또는 대기 없이 처리 |
흐름 | 응답까지 대기 | 응답은 나중에 처리 |
자원 활용 | 비효율적 | 효율적 (Non-blocking) |
예외 처리 | 단순 | 복잡 (callback, await 등) |
사용 예 | 로그인, 결제 API | 이메일 발송, Kafka 메시지 발행 |
결론
운영체제는 효율적인 자원 분배와 사용자 응답성을 보장하기 위해 비동기 방식을 적극적으로 채택해왔다. 오늘날 백엔드 시스템, 메시징 큐, 이벤트 기반 프레임워크 등에서도 이 철학은 그대로 이어지고 있다.