동기, 비동기 vs 블로킹, 논블로킹

Hunn·2025년 7월 27일

CS

목록 보기
12/15

들어가며

최근에 백엔드 면접스터디에 들어가면서 매주 다양한 주제를 가지고 꼬리질문에 대한 답변을 연습하고 있다. 그 중에서 나왔던 주제중에 하나가 바로 동기 비동기의 차이 였고, 여기서 꼬리질문으로 나왔던게 블로킹 논블로킹의 차이였다.
동기 비동기에 대해서는 잘 알고있다고 생각했지만 알고보니 허점 투성이였고, 따라서 이번에는 이에 대해서 공부한 내용을 정리해보고자 한다.

이 글의 목표는 각 개념의 핵심적인 차이를 명확히 정의하고, 이들이 어떻게 조합되어 실제 로 동작하는지 구체적인 사례와 함께 완벽하게 이해해보고자 한다.

1. 핵심 개념 정의: 제어권 vs 결과 처리 책임

가장 중요한 첫 단계는 네 가지 용어를 두 개의 축으로 나누어 이해하는 것이다.

가. 블로킹(Blocking) vs 논블로킹(Non-blocking): '제어권'의 문제

이 둘을 나누는 기준은 단 하나, 호출된 함수가 호출한 함수에게 '제어권(Control)'을 즉시 반환하느냐이다.

  • 블로킹 (Blocking) 호출된 함수는 자신의 작업이 모두 끝날 때까지 제어권을 호출한 함수에게 넘겨주지 않는다. 따라서 호출한 함수는 호출된 함수가 반환될 때까지 다음 코드를 실행하지 못하고 멈춰 선다.
  • 논블로킹 (Non-blocking) 호출된 함수는 자신의 작업 완료 여부와 상관없이, 호출 즉시 제어권을 호출한 함수에게 넘겨준다. 따라서 호출한 함수는 멈추지 않고 바로 다음 코드를 실행할 수 있다.

나. 동기(Synchronous) vs 비동기(Asynchronous): '결과 처리'의 문제

이 둘을 나누는 기준은 호출된 함수의 작업 완료를 누가, 어떻게 신경 쓰느냐이다.

  • 동기 (Synchronous) 호출한 함수가 호출된 함수의 작업 완료를 직접 신경 쓴다. 즉, 결과가 반환될 때까지 기다리거나, 혹은 직접 주기적으로 상태를 확인하며 결과가 나왔는지 적극적으로 체크해야 한다. 작업의 순서와 결과 반환의 순서가 일치한다.
  • 비동기 (Asynchronous) 호출한 함수는 호출된 함수의 작업 완료를 신경 쓰지 않는다. 대신 호출된 함수가 자신의 작업을 마치면 '알림(Notification)'이나 '콜백(Callback)'을 통해 호출한 함수에게 결과를 전달한다. 작업의 순서와 결과 반환의 순서가 일치하지 않을 수 있다.

2. 네 가지 조합으로 실전 이해하기

이 개념들은 독립적으로 존재하기보다 조합되어 나타난다. 커피숍 주문에 비유하여 네 가지 조합을 살펴보자.

  • : 호출하는 함수(메인 스레드)
  • 점원: 호출되는 함수(I/O, API)
  • 커피: 작업 결과물

가. 동기 + 블로킹 (Sync-Blocking)

가장 흔하고 직관적인 모델

  • 상황: 커피를 주문하고, 카운터 앞에서 커피가 나올 때까지 아무것도 안 하고 기다린다. 커피가 나오면 받아서 자리에 간다.
  • 분석:
    • 블로킹: 점원이 커피를 만드는 동안 '나'는 제어권을 뺏겨 다른 일을 할 수 없다.
    • 동기: 커피가 완성되는 시점과 내가 커피를 받는 시점이 일치하며, 결과(커피)를 내가 직접 챙겨야 한다.
  • 사례: 대부분의 기본적인 함수 호출, Java의 InputStream.read()

나. 동기 + 논블로킹 (Sync-Non-blocking)

호출자가 계속 확인해야 하는 모델

  • 상황: 커피를 주문하고, 일단 자리에 돌아온다. 하지만 다른 일은 못 하고 10초에 한 번씩 카운터에 가서 "제 커피 나왔나요?" 라고 계속 물어본다.
  • 분석:
    • 논블로킹: 점원은 주문만 받고 제어권을 즉시 돌려주므로, 나는 자리에 돌아올 수 있다.
    • 동기: 하지만 커피가 완성되었는지는 '나'의 책임이므로, 내가 직접 주기적으로 확인(Polling)해야 한다.
  • 사례: 운영체제의 스핀락(Spinlock), Non-blocking I/O에서의 반복적인 상태 확인

다. 비동기 + 논블로킹 (Async-Non-blocking)

가장 효율적인 현대적 비동기 모델

  • 상황: 커피를 주문하고, 진동벨을 받아 내 자리로 돌아와서 다른 일(웹서핑, 독서 등)을 한다. 나중에 진동벨이 울리면(Callback) 커피를 찾으러 간다.
  • 분석:
    • 논블로킹: 점원은 주문만 받고 제어권을 즉시 돌려준다.
    • 비동기: 점원이 커피 완성을 '진동벨'로 알려주므로, 나는 결과에 신경 쓰지 않아도 된다.
  • 사례: Node.js의 대부분의 I/O 처리, Java의 CompletableFuture, 웹의 Ajax 요청

라. 비동기 + 블로킹 (Async-Blocking)

가장 드물지만 특정 상황에서 사용되는 모델

  • 상황: 내가 대리인(B)에게 "커피 주문 좀 해와" 라고 시킨다. B는 점원과 비동기적으로(진동벨을 받아) 소통한다. 하지만 나는 B가 커피를 들고 돌아올 때까지 그 자리에서 아무것도 안 하고 기다린다.
  • 분석:
    • 블로킹: '나'는 대리인 B가 돌아올 때까지 제어권을 뺏겨 다른 일을 못 하고 멈춰있다.
    • 비동기: 실제 작업(커피 주문)은 대리인 B와 점원 사이에서 비동기적으로 처리된다.
  • 사례: Java에서 Future 객체의 get() 메서드를 호출하는 경우. 비동기 작업을 요청했지만, 그 결과를 받을 때까지 메인 스레드는 블로킹된다.

3. 구체적 사례 분석

가. System.out.println()은 동기-블로킹인가?

그렇다. main 메서드가 println을 호출하는 순간, 제어권은 println에게 넘어간다. println이 콘솔 I/O 작업을 마치고 제어권을 돌려줄 때까지 main 메서드는 다음 코드로 진행하지 못하고 일시 정지 상태가 된다. 이 과정이 워낙 순식간에 일어나 인지하지 못할 뿐, 명백한 동기-블로킹 호출이다.

나. 운영체제의 스핀락(Spinlock)은 왜 동기-논블로킹인가?

스핀락은 임계 구역에 진입하기 위해 락(Lock)을 얻으려 할 때, 스레드를 수면 상태(Blocking)로 만들지 않는다. 대신 CPU를 점유한 채로 "락을 얻을 수 있는가?"를 계속해서 확인(Busy-Waiting)한다.

  • 동기: 락 획득 여부를 스레드 스스로 계속 확인해야 한다.
  • 논블로킹: 락을 확인하는 각 시도는 즉시 반환되며, 스레드는 수면 상태로 전환되지 않는다.

이는 락을 얻지 못하면 즉시 스레드를 대기 큐로 보내고 CPU 제어권을 OS에 넘기는 뮤텍스(Mutex, 동기-블로킹)와 명확히 대비된다.

4. 결론

네 가지 개념을 명확히 구분하는 것이 중요하다.

  • 블로킹 vs 논블로킹'제어권'이 누구에게 있는가의 관점이다.
  • 동기 vs 비동기'결과 처리의 책임'이 누구에게 있는가의 관점이다.

자바 개발자라면 흔히 MVC 동기 - 블로킹 또는 WebFlux 비동기 - 논블로킹이 대표적으로 떠올랐을것이고, 사실상 이를 동기와 비동기라고 착각하고 있을 수도 있다. 하지만 이 두 가지 축을 기준으로 네 가지 조합을 이해한다면, 복잡한 I/O 처리나 멀티스레딩 환경에서 발생하는 문제를 분석하고 최적의 아키텍처를 선택하는 데 큰 도움이 될 것이라고 생각한다!

참고 자료 (References)

  1. Java Concurrency in Practice (Brian Goetz et al.)
  2. Node.js 공식 문서 - Overview of Blocking vs. Non-Blocking
  3. Operating System Concepts (Abraham Silberschatz et al.)
  4. Oracle Java Documentation - java.util.concurrent.Future
profile
명확한 문제 정의를 가장 중요시 여기는 개발자, 채기훈입니다.

1개의 댓글

comment-user-thumbnail
2025년 7월 28일

손님 점원 커피
진동벨 (논블로킹)
예시가 이해하는데 확 와닿네요 감사합니다~

비동기 블로킹은
손님이 점원에게 커피 주문하고 진동벨을 받았지만
점원 앞에서 가만히 기더리는 형태겠네요?

asnyc/await구조에서 async 코드 다음 줄 에 await을 남발하면
흡사 비동기 블로킹 구조가 되겠네용

답글 달기