동기·비동기와 블로킹·논블로킹 차이: 통신과 코드에서 헷갈리지 않기

반응형

동기(synchronous)와 비동기(asynchronous)는 문맥에 따라 다른 질문에 답한다. 직렬 통신에서는 클럭을 어떻게 맞추는지, 프로그래밍 API에서는 작업 완료를 언제 돌려받는지를 가리킬 수 있다. 여기에 블로킹·논블로킹, 동시성·병렬성까지 섞으면 같은 말처럼 보이기 쉽다.

핵심은 용어보다 먼저 무엇을 기다리고 누가 진행할 수 있는가를 묻는 것이다.

한눈에 구분하기

질문 한쪽 다른 쪽
완료 관계 호출과 결과가 같은 흐름에서 완료되는가? 동기 비동기
실행 대기 호출한 스레드가 기다리며 멈추는가? 블로킹 논블로킹
진행 구조 여러 작업의 진행이 겹치는가? 순차 동시성
물리 실행 같은 순간에 실제로 여러 작업이 실행되는가? 단일 실행 병렬성

이 축은 관련 있지만 동일하지 않다. 비동기 API도 결과를 즉시 기다리면 사실상 동기적으로 사용할 수 있고, 논블로킹 호출을 반복 polling하면 CPU를 낭비할 수 있다.

직렬 통신에서의 동기와 비동기

하드웨어 통신 문맥에서는 보통 타이밍 기준을 어떻게 공유하는지가 핵심이다.

동기식 직렬 통신

SPI나 I²C처럼 데이터 신호와 함께 클럭 신호를 전달한다. 송신자와 수신자는 클럭 edge를 기준으로 비트를 해석한다.

비동기식 직렬 통신

UART처럼 별도의 공유 클럭 선 없이 양쪽이 미리 약속한 baud rate와 프레임의 시작·정지 비트를 이용한다. 클럭이 “전혀 없다”기보다 각 장치가 자체 클럭으로 신호를 샘플링하고 프레임 단위로 다시 맞춘다고 보는 편이 정확하다.

이 구분을 웹 요청의 동기·비동기와 그대로 연결하면 안 된다. 서로 다른 계층에서 같은 단어를 쓰고 있을 뿐이다.

프로그래밍 API에서의 동기와 비동기

동기 호출은 일반적으로 호출한 흐름에서 작업 결과가 확정된 뒤 다음 단계로 넘어간다.

data = read_file()
handle(data)

비동기 API는 작업을 시작한 뒤 미래의 완료를 task, future, callback이나 event 형태로 전달한다.

data = await read_file()
handle(data)

await가 보이니 프로그램 전체가 멈추는 것처럼 보이지만, event loop는 해당 coroutine이 I/O를 기다리는 동안 실행 가능한 다른 task를 진행할 수 있다. 반면 한 coroutine이 긴 CPU 계산을 양보 없이 수행하면 event loop도 막힌다.

비동기는 동시에 실행된다는 뜻이 아니다

  • 동시성(concurrency): 여러 작업이 같은 시간 구간에 진행된다.
  • 병렬성(parallelism): 여러 CPU core 등에서 같은 순간 실제로 실행된다.

단일 스레드 event loop도 I/O 대기 사이에 작업을 전환해 동시성을 만들 수 있다. CPU-bound 작업을 병렬화하려면 별도 프로세스, 스레드 또는 가속기 같은 다른 실행 자원이 필요하다.

네트워크 시스템에서는 완료 계약을 본다

HTTP 자체를 무조건 “동기 프로토콜”, 메시지 큐를 무조건 “비동기 프로토콜”이라고 부르기보다 애플리케이션 계약을 본다.

요청·응답 계약

클라이언트가 응답을 받아야 다음 업무를 확정할 수 있다면 동기적인 의존 관계가 있다. 네트워크 라이브러리가 논블로킹 I/O를 사용해도 업무 계약은 동기일 수 있다.

메시지·이벤트 계약

생산자가 메시지 수락만 확인하고 소비자의 최종 처리를 기다리지 않는다면 비동기적인 관계다. 대신 재시도, 중복, 순서, 처리 결과 조회와 실패 보상 설계가 필요하다.

선택 기준

상황 먼저 고려할 방식 함께 확인할 것
결과가 있어야 다음 규칙을 판단 동기 요청·응답 timeout, 취소, 부분 실패
독립 작업을 오래 기다림 비동기 I/O backpressure, 동시 실행 수
순간 트래픽을 흡수 큐 기반 비동기 처리 지연 허용치, 중복·순서
짧고 단순한 내부 흐름 동기 코드 복잡성 대비 이익
CPU 계산이 큼 병렬 실행 검토 core 수, 직렬화 비용

비동기 방식은 기다리는 시간을 다른 작업에 쓸 수 있지만 상태와 오류 처리가 복잡해진다. 동기 방식은 흐름이 읽기 쉽지만 느린 의존성을 그대로 기다리면 자원을 비효율적으로 잡아둘 수 있다. 어느 쪽이 우월한 것이 아니라 지연, 처리량, 실패 모델과 운영 복잡성을 함께 비교해야 한다.

헷갈릴 때의 네 질문

  1. 호출 직후 결과가 필요한가, 완료 통지만 나중에 받아도 되는가?
  2. 기다리는 동안 현재 스레드나 event loop가 다른 일을 할 수 있는가?
  3. 여러 작업은 진행만 겹치는가, 실제로 병렬 실행되는가?
  4. timeout·취소·재시도·중복 처리를 누가 책임지는가?

이 네 가지를 분리하면 “비동기니까 빠르다”, “await가 있으니 블로킹이다” 같은 단정을 피할 수 있다.

참고 문서

반응형
KEEP READING
카테고리 전체 보기 →

댓글