Post

동기 vs 비동기, 블로킹 vs 논블로킹, 그리고 웹 프레임워크에서의 비동기 처리

동기, 비동기, 블로킹, 논블로킹, with 스프링, Django, Flask

동기 vs 비동기, 블로킹 vs 논블로킹, 그리고 웹 프레임워크에서의 비동기 처리

동기 vs 비동기, 블로킹 vs 논블로킹, 그리고 웹 프레임워크에서의 비동기 처리

1. 동기(Synchronous) vs 비동기(Asynchronous)

1.1 동기(Synchronous)

  • 함수 A가 동기로 함수 B를 호출하면,
    • A는 B의 작업이 완료될 때까지 대기한다.
    • 즉, 작업이 순차적으로 진행되므로, B가 끝나야 A의 다음 로직이 진행된다.

1.2 비동기(Asynchronous)

  • 함수 A가 비동기로 함수 B를 호출하면,
    • A는 B의 작업 완료를 기다리지 않고 바로 다른 로직을 수행한다.
    • 따라서, 작업 순서가 ‘순차’가 아닐 수 있고, B는 완료 시점에 콜백(Callback)이나 이벤트(Event)로 결과를 통보할 수 있다.

정리

  • 동기: “기다린다” (작업이 순차적)
  • 비동기: “기다리지 않는다” (작업이 병렬적·비순차적)

2. 블로킹(Blocking) vs 동기(Synchronous)의 차이

두 용어는 비슷해 보이지만, 엄밀히 보면 의미가 다르다.

  1. 동기(Synchronous)
    • 호출된 함수(메서드)가 작업을 마칠 때까지 호출한 측이 기다린다.
    • 예) “함수 A → 함수 B 호출” 시 B가 끝날 때까지 A는 다음 로직 수행 불가.
  2. 블로킹(Blocking)
    • 함수(메서드)를 호출한 쓰레드가, 결과나 자원을 기다리느라 실행을 멈추고 제어권을 반환받지 못하는 상태.
    • “호출자는 기다리는 동안 아무 일도 못 하고 멈춰 있음”을 가리킨다.

동기는 “작업 흐름이 순차적이냐 비순차적이냐”를,
블로킹은 “호출한 쪽이 실행을 멈추고 있느냐 아니냐”를 나타내므로, 서로 약간 다른 관점이다.


3. 스프링(Spring)에서 비동기 처리하기

3.1 @Async 어노테이션

스프링에서는 메서드에 @Async를 적용해 비동기 로직으로 동작하게 만들 수 있다.

  • 프록시(Proxy)를 통해 메서드 호출을 가로채어, 별도의 스레드 풀에서 실행한다.
  • 호출하는 쪽은 즉시 반환받아, 다른 로직을 진행할 수 있다.

3.1.1 주의사항

  1. 예외 전파 문제
    • @Async 메서드에서 발생하는 예외는 호출자에게 자동으로 전파되지 않는다.
    • 비동기 메서드의 예외를 처리하려면 AsyncUncaughtExceptionHandler 등을 별도로 등록해야 한다.
  2. 클래스 내부 호출 시 비동기 동작 불가
    • @Async는 프록시 기반이므로, 자기 자신 클래스 내부의 메서드 호출은 프록시가 적용되지 않는다.
    • 따라서 같은 클래스 안에서 직접 호출하면 비동기가 아닌 동기 방식으로 실행된다.
  3. 트랜잭션 범위
    • 별도의 스레드에서 실행되므로, 상위 트랜잭션과 동기화되지 않는다.
    • 각 비동기 메서드는 독립적인 트랜잭션 범위를 갖게 된다.

4. Django와 Flask에서의 동기/비동기 처리

4.1 Django

4.1.1 전통적인 동기 모델 (WSGI)

  • Django는 오랫동안 WSGI(Web Server Gateway Interface) 기반으로 동작해 왔다.
  • WSGI는 기본적으로 동기적 요청-응답 모델이며, 멀티쓰레드/멀티프로세스로 확장 가능한 구조이다.

4.1.2 ASGI 지원 (Django 3.0+)

  • Django 3.0부터는 ASGI(Asynchronous Server Gateway Interface)를 공식 지원한다.
  • 이를 통해 Django도 비동기 요청 처리가 가능해졌으며, 웹소켓(WebSocket) 같은 실시간 통신도 처리할 수 있다.
  • Django Channels는 ASGI 기반으로, 채팅이나 실시간 알림 등 비동기 통신을 지원하기 위해 만들어진 공식 라이브러리다.

주의

  • Django의 코어 일부와 서드파티 앱 중에는 아직 순수 동기 방식을 전제한 경우도 있을 수 있다.
  • 비동기 뷰(Async View)와 DB 접근 등을 혼용할 때, 동기/비동기 전환(오버헤드)이 발생할 수 있으니 구조 설계를 신중하게 해야 한다.

4.2 Flask

4.2.1 전통적 Flask (WSGI)

  • Flask는 WSGI 기반의 간단한 마이크로 프레임워크로 시작했다.
  • 기본적으로 동기 요청-응답 방식을 사용하며, 멀티쓰레드/멀티프로세스로 스케일링할 수 있다.

4.2.2 비동기 지원

  • Flask 2.x 이상에서는 제한적으로 async def 뷰 함수를 지원하기 시작했다.
  • 다만, 내부적으로 WSGI 서버를 쓸 경우 여전히 동기 처리 흐름이므로, 완전한 비동기 성능 이점을 얻기 어렵다.
  • Quart 라이브러리는 Flask API와 호환되면서 ASGI 기반으로 동작하므로, Flask 스타일로 진정한 비동기 환경을 원하는 경우 Quart를 고려하기도 한다.
  • 또한, gevent, eventlet 등을 사용해 Flask 애플리케이션을 비동기 IO로 동작하도록 구성할 수 있다.

정리

  • Flask는 기본적으로 동기. 부분적으로 async def를 지원하지만, ASGI 완전 지원은 Quart 같은 대안 사용.
  • Django는 Django Channels(ASGI)로 웹소켓, 비동기 뷰 등을 공식적으로 지원하되, 일부는 동기 기반과 혼재될 수 있음.

5. 정리

  • 동기 vs 비동기: 호출한 함수가 호출된 함수의 작업 완료를 기다리는지(동기) vs 기다리지 않는지(비동기).
  • 블로킹 vs 논블로킹: 호출한 쓰레드가 자원 혹은 결과를 대기하느라 멈춰 있는지(블로킹) vs 멈추지 않고 다른 일을 할 수 있는지(논블로킹).
  • 스프링(@Async): 간편하게 비동기 처리를 적용할 수 있지만, 예외 전파, 트랜잭션 범위, 클래스 내부 호출 등에 주의해야 함.
  • Django: 전통적으로 WSGI 기반 동기 모델이지만, 3.0 이후 ASGI를 지원하여 비동기 뷰와 웹소켓 같은 실시간 처리도 가능해짐(Django Channels).
  • Flask: 기본적으로 WSGI 기반 동기 모델이지만, 2.x 이상의 async def 일부 지원과 Quart, gevent, eventlet 등으로 비동기 처리 가능.

현대 애플리케이션에서는 고성능·확장성을 위해 비동기(Async) 및 논블로킹(IO) 방식을 많이 활용한다. 그러나 동시성·병렬성 이슈, 예외 처리, 트랜잭션 처리 등을 세심하게 고려해야 한다. 위 개념과 프레임워크별 주의사항을 잘 이해하고 적용한다면, 더욱 효율적이고 반응성이 뛰어난 서비스를 만들 수 있을 것이다.

This post is licensed under CC BY 4.0 by the author.