Published on

블로킹과 논블로킹의 차이

🔄 블로킹과 논블로킹, 그리고 동기/비동기까지

개발을 하다 보면 자주 마주치는 개념들, 블로킹/논블로킹과 동기/비동기. 이들은 서로 다른 관점에서 시스템의 동작 방식을 설명하지만, 실제로는 밀접하게 연관되어 있습니다. 오늘은 이 개념들을 자세히 살펴보고, 실제 웹 서비스에서 어떻게 활용되는지 알아보겠습니다.

1. 블로킹(Blocking) vs 논블로킹(Non-blocking)

🔍 정의

  • 블로킹: I/O 작업(read(), db.query() 등)을 수행할 때, 결과가 올 때까지 해당 쓰레드(또는 코루틴)가 완전히 멈춰 기다리는 방식
  • 논블로킹: I/O 작업을 수행할 때, "지금 당장 처리할 데이터가 없더라도" 즉시 반환하여, 대기하지 않고 다음 로직을 실행할 수 있는 방식

💡 특징 비교

특성블로킹논블로킹
호출 시 행동결과 대기즉시 반환
흐름 제어기다린 후 다음 실행바로 다음 실행, 결과는 폴링/이벤트로 처리
구현 복잡도낮음높음 (상태 관리 필요)
동시성낮음 (쓰레드 낭비)높음 (한 쓰레드로 많은 I/O 감당)

⚖️ 장단점

블로킹

  • 장점: 직관적, 예외 처리·디버깅 용이
  • 단점: I/O 지연 시 전체 쓰레드 풀이 잠김

논블로킹

  • 장점: I/O 대기 중에도 다른 작업 수행 가능, 단일 쓰레드로 높은 동시성
  • 단점: 폴링 로직·이벤트 관리 필요, 구현 난이도 상승

💻 코드 예제

# 블로킹 방식
def blocking_io():
    print("데이터베이스 쿼리 시작...")
    result = db.query("SELECT * FROM users")  # 쿼리 완료까지 대기
    print("쿼리 결과:", result)
    return result

# 논블로킹 방식
async def non_blocking_io():
    print("데이터베이스 쿼리 시작...")
    future = db.query_async("SELECT * FROM users")  # 즉시 반환
    print("다른 작업 수행 가능")
    result = await future  # 결과가 필요할 때까지 대기
    return result

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

🔍 정의

  • 동기: 호출자가 "결과가 돌아올 때까지" 흐름을 멈추고, 리턴된 값을 받아야만 다음 코드를 실행
  • 비동기: 호출 즉시 제어권을 돌려받아 다른 일을 수행하고, 결과는 콜백/Promise/Future 등으로 나중에 처리

💡 특징 비교

특성동기비동기
호출 이후 흐름결과 대기 후 순차 실행즉시 리턴, 후속 처리 콜백
응답 지연 영향직접 반영 (지연 시 전체 멈춤)지연 구간에 다른 작업 수행
구현 난이도낮음높음 (콜백·상태 관리 필요)

3. 네 가지 조합

블록/논블동기비동기대표 사례
블로킹 + 동기쓰레드가 I/O 완료까지 멈춤Django/Flask WSGI
논블로킹 + 동기I/O 즉시 리턴, 폴링·확인 후 순차 처리준비 상태만 체크 → 동기 로직
블로킹 + 비동기API 호출은 비동기, 워커 내부는 블로킹워커가 콜백(완료 알림)FastAPI + Celery
논블로킹 + 비동기이벤트 루프에서 I/O 즉시 반환 → 결과 콜백결과 도착 시점에 비동기 처리Node.js, asyncio

4. 웹 서비스 적용 관점

🔄 아키텍처별 적용

  1. 소규모 CRUD API

    • 블로킹 + 동기: Django, Flask/WSGI
    • 적합한 경우: 간단한 데이터 처리, 낮은 동시성 요구
  2. 실시간·고동시성

    • 논블로킹 + 비동기: FastAPI(asyncio), Node.js WebSocket
    • 적합한 경우: 실시간 채팅, 알림 시스템
  3. 무거운 백그라운드 작업 분리

    • 블로킹 + 비동기: Celery 워커에 이미지/동영상 처리 위임
    • 적합한 경우: 파일 처리, 이메일 발송
  4. 폴링이 필요할 때

    • 논블로킹 + 동기: Redis 상태만 중간중간 확인하고, 준비되면 동기 처리
    • 적합한 경우: 작업 상태 모니터링

🔄 Gunicorn 워커 구조

# Gunicorn 설정 예시
workers = 2
worker_class = 'sync'  # 또는 'uvicorn.workers.UvicornWorker'

# sync 워커의 경우
# - 각 워커는 독립된 OS 프로세스
# - 싱글스레드, 블로킹 I/O
# - 최대 2개의 요청을 병렬 처리

5. 결론

블로킹/논블로킹과 동기/비동기는 서로 다른 관점에서 시스템의 동작을 설명하지만, 실제로는 밀접하게 연관되어 있습니다:

  • 블로킹/논블로킹: "I/O 호출 시 쓰레드를 멈추느냐"를 결정
  • 동기/비동기: "결과를 받아 흐름을 멈추느냐"를 결정

웹 서비스에서는 이 두 가지 특성을 적절히 조합하여 성능과 개발 생산성 사이에서 균형을 맞추는 것이 중요합니다. 상황에 따라 적절한 방식을 선택하고, 필요한 경우 여러 방식을 조합하여 사용하는 것이 현명한 접근 방법입니다.

💡 Tip: 실제 프로젝트에서는 요구사항과 시스템의 특성을 잘 파악하여, 적절한 조합을 선택하는 것이 중요합니다. 무조건 비동기/논블로킹이 좋은 것이 아니라, 상황에 맞는 선택이 필요합니다.