Zzangss' 기술 블로그

태그

제어권과 완료 통지로 이해하는 동기·비동기, 블로킹·논블로킹

동기·비동기와 블로킹·논블로킹을 공부하다 보면 비동기는 논블로킹이고 동기는 블로킹이라는 설명을 자주 만난다. 두 용어를 같은 기준으로 묶어서 생기는 혼란이다. 이번 글에서는 제어권과 작업 완료라는 두 키워드로 나눠 정리해보자.

1. 먼저 두 가지 질문으로 나누기

네 용어는 다음 두 질문으로 구분할 수 있다.

구분 질문 판단 기준
블로킹/논블로킹 함수를 호출한 뒤 제어권이 바로 돌아오는가? 호출자의 다음 코드를 실행할 수 있는가?
동기/비동기 작업 완료를 누가 챙기는가? 호출자가 직접 확인하는가, 호출받은 쪽이 알려주는가?

블로킹과 논블로킹은 제어권, 동기와 비동기는 완료 통지에 관한 개념이다.

질문이 다르기 때문에 두 기준은 독립적으로 조합된다. 동기이면서 논블로킹일 수 있고, 비동기이면서 블로킹인 흐름도 만들 수 있다.

1.1. 제어권(control)

제어권은 호출한 사람이 다음 행동을 할 수 있는지를 나타낸다.

블로킹(blocking)은 작업이 끝날 때까지 제어권을 돌려주지 않는다. 호출한 사람은 그 자리에서 기다려야 한다.

논블로킹(non-blocking)은 작업이 끝나지 않았어도 제어권을 먼저 돌려준다. 호출한 사람은 다른 일을 하거나 나중에 다시 확인할 수 있다.

여기서 바로 돌아온다는 말은 처리 시간이 항상 짧다는 뜻이 아니다. 최종 결과가 나올 때까지 호출자를 붙잡아 두지 않는다는 의미이다.

1.2. 작업 완료(completion)

동기와 비동기는 작업 완료를 누가 챙기는지로 나눈다.

동기(synchronous) 방식에서는 호출자가 완료를 직접 확인한다. 결과가 나올 때까지 기다려서 받는 경우뿐 아니라, 나중에 상태를 다시 조회하는 폴링(polling)도 호출자가 완료를 챙기는 방식이다.

비동기(asynchronous) 방식에서는 호출받은 쪽이 작업을 마친 뒤 알려준다. 콜백(callback), 이벤트(event), 메시지, 진동벨이 여기에 해당한다.

정리하면 다음과 같다.

구분 호출한 사람의 행동
동기 “끝났나요?”를 직접 확인한다.
비동기 “끝나면 알려주세요.”라고 맡긴다.

2. 관찰 범위를 먼저 정해야 한다

같은 시스템 안에서도 어떤 동작을 보느냐에 따라 답이 달라진다.

예를 들어 비동기 작업을 요청하고 접수 결과를 바로 받았다고 해보자. 요청 단계에서는 제어권을 돌려받았다. 하지만 그 직후 완료될 때까지 기다리는 기능을 호출한다면, 그 대기 단계에서는 다시 제어권을 잃는다.

관찰 대상 판단
작업 요청 완료 전에 돌아왔다면 논블로킹
완료 확인 호출자가 직접 확인하면 동기
완료 알림 작업한 쪽이 알려주면 비동기
별도의 대기 결과가 나올 때까지 멈추면 블로킹

따라서 “이 시스템은 비동기다”라는 말만으로는 충분하지 않다. HTTP 요청, 전체 비즈니스 작업, 네트워크 I/O처럼 어느 범위를 보고 있는지 먼저 정해야 한다.

3. 동시성과 병렬성

동시성(concurrency)은 동기의 반대말이 아니다.

동시성은 여러 작업이 겹치는 시간 동안 진행되는 구조이다. 하나의 CPU 코어에서도 실행 시간을 나누면 여러 작업이 번갈아 진행된다.

시간 →

Task A  실행 ─── 대기 ─── 실행
Task B  ─── 실행 ─── 대기 ─── 실행

병렬성(parallelism)은 여러 작업이 실제 같은 시점에 실행되는 경우이다. 여러 CPU 코어가 각각 다른 작업을 실행하는 상황이 대표적이다.

Core 1  Task A ───────────────
Core 2  Task B ───────────────
구분 중심 질문
동시성/병렬성 여러 작업을 어떻게 배치하고 실행하는가?
동기/비동기 작업 완료를 누가 확인하고 알려주는가?
블로킹/논블로킹 호출 뒤 제어권이 언제 돌아오는가?

4. 한 장으로 보는 네 가지 조합

카페에서 음료를 주문하는 상황으로 네 가지 조합을 비교해보자.

카페 주문으로 비교한 동기·비동기와 블로킹·논블로킹의 네 가지 조합

왼쪽 위는 동기·블로킹, 오른쪽 위는 동기·논블로킹, 왼쪽 아래는 비동기·블로킹, 오른쪽 아래는 비동기·논블로킹을 나타낸다.

같은 주문이라도 두 가지를 따로 확인해야 한다.

  1. 주문한 뒤 손님이 다른 일을 할 수 있는가? → 제어권
  2. 음료가 완성됐는지 손님이 확인하는가, 진동벨이 알려주는가? → 완료 통지
위치 조합 카페에서 일어나는 일
왼쪽 위 동기 + 블로킹 손님이 카운터 앞에서 기다리다가 음료를 직접 받는다.
오른쪽 아래 비동기 + 논블로킹 진동벨을 받고 다른 일을 하다가 알림이 오면 음료를 받는다.
오른쪽 위 동기 + 논블로킹 다른 일을 할 수 있지만 손님이 픽업대를 직접 확인한다.
왼쪽 아래 비동기 + 블로킹 진동벨이 알려주지만 손님은 다른 일을 하지 않고 기다린다.

4.1. 동기 + 블로킹

손님이 주문한 뒤 카운터 앞을 떠나지 않고 기다린다. 음료가 나오면 그 자리에서 직접 받아간다.

관점 판단
제어권 음료가 나올 때까지 손님에게 돌아오지 않는다.
작업 완료 손님이 같은 흐름에서 직접 확인한다.

기술에서는 일반적인 JDBC 조회나 파일 읽기가 이 흐름과 비슷하다. 결과가 돌아와야 다음 작업으로 넘어간다.

가장 단순하고 이해하기 쉬운 방식이지만, 느린 작업이 많아지면 기다리는 스레드도 함께 늘어난다.

4.2. 비동기 + 논블로킹

손님이 주문하고 진동벨을 받은 뒤 자리로 돌아간다. 기다리는 동안 노트북으로 다른 일을 하고, 음료가 완성되면 진동벨이 울린다.

관점 판단
제어권 주문 직후 손님에게 돌아온다.
작업 완료 카페가 진동벨로 알려준다.

기술에서는 콜백, 이벤트, 이벤트 루프가 대표적이다. 요청한 쪽은 다른 작업을 계속하고, 작업이 끝나면 완료 이벤트를 받아 처리한다.

비동기와 논블로킹이 함께 쓰이는 가장 익숙한 경우이다.

4.3. 동기 + 논블로킹

이번에는 카페에서 진동벨을 주지 않는다고 생각해보자.

손님은 주문 후 카운터를 떠날 수 있다. 하지만 음료가 완성됐는지는 직접 픽업대를 확인해야 한다. 아직 나오지 않았다면 다른 일을 하다가 나중에 다시 확인한다.

관점 판단
제어권 확인할 때마다 바로 손님에게 돌아온다.
작업 완료 손님이 픽업대를 직접 확인한다.

기술에서는 논블로킹 I/O의 상태나 반환값을 호출자가 반복해서 확인하는 경우가 여기에 해당한다.

호출할 때마다 바로 돌아오므로 논블로킹이다. 그러나 완료됐는지는 호출자가 챙기므로 동기이다. 확인 주기가 너무 짧으면 바쁜 대기(busy polling)로 CPU를 낭비할 수 있다.

4.4. 비동기 + 블로킹

네 조합 중 가장 헷갈리는 경우이다.

카페는 음료가 완성되면 진동벨로 알려준다. 완료 통지 방식은 비동기이다. 그런데 손님이 진동벨을 받은 뒤 다른 일을 하지 않고 대기 의자에 앉아 알림만 기다린다면 호출자 쪽 흐름은 블로킹된다.

관점 판단
제어권 별도의 대기 단계에서 다시 잃는다.
작업 완료 카페가 진동벨로 알려준다.

기술에서는 비동기 작업을 시작한 뒤 호출자가 await(), join(), get() 같은 대기 기능으로 결과를 기다릴 때 나타난다.

작업한 쪽이 완료를 알려주므로 비동기이지만, 현재 스레드는 대기 기능 때문에 멈춘다. 기존 동기 코드나 테스트에서 비동기 작업의 결과를 기다릴 때 볼 수 있다.

한 함수 호출이 동시에 비동기이면서 블로킹이라는 뜻은 아니다. 비동기 작업의 완료 통지와 대기 중인 스레드의 제어권을 서로 다른 경계에서 본 결과이다.

5. 이 글에서 기억할 키워드

이 글에서는 다음 네 가지 키워드만 기억하면 된다.

키워드 의미
제어권 호출한 쪽이 다음 행동을 할 수 있는 상태
블로킹 대기 작업이 끝날 때까지 제어권을 돌려받지 못하는 상태
폴링(polling) 호출한 쪽이 완료 여부를 반복해서 확인하는 방식
콜백·이벤트 작업한 쪽이 완료 사실을 알려주는 방식

6. 정리

구분 확인할 질문
블로킹/논블로킹 제어권이 언제 돌아오는가?
동기/비동기 작업 완료를 누가 확인하고 알려주는가?

제어권과 완료 통지는 서로 다른 질문이다. 그래서 동기 + 블로킹, 비동기 + 논블로킹, 동기 + 논블로킹, 비동기 + 블로킹의 네 조합이 나온다.

새로운 흐름을 볼 때는 다음 두 가지부터 확인해보자.

  1. 최종 결과가 나오기 전에 제어권을 돌려주는가?
  2. 완료를 호출자가 직접 확인하는가, 작업한 쪽이 알려주는가?