Swift & Objective-C

동기 vs 비동기, 블로킹 vs 논블로킹 — 2×2 조합 완벽 정리

동기·비동기는 작업 완료를 누가 챙기느냐의 축이고, 블로킹·논블로킹은 제어권을 바로 돌려주느냐의 축입니다. 두 축이 독립이라 2×2 네 조합이 모두 존재하는 이유를 Swift 예시와 함께 정리했습니다.

6분 읽기
동기 vs 비동기, 블로킹 vs 논블로킹 — 2×2 조합 완벽 정리 대표 이미지

동기와 비동기는 대충 알겠는데, 블로킹과 논블로킹까지 나오면 머리가 복잡해집니다.

관련 글 프로세스 vs 스레드, 기술면접 1순위 질문 완벽 정리 (메모리 구조로 이해)에서 배경 개념과 이어지는 적용 사례를 함께 확인할 수 있습니다. Dispatch

“동기 = 블로킹, 비동기 = 논블로킹 아닌가?” 싶지만, 이 둘은 서로 다른 축입니다. 조합하면 네 가지 경우가 나오고 실제로 넷 다 존재해요.

네트워크 코드, async/await, Node.js 같은 이벤트 루프 이야기가 나올 때마다 등장하는 개념입니다. 한 번 축을 제대로 잡아두면 두고두고 써먹어요.

이 글에서는 두 축을 정확히 가르고, 2×2 조합을 예시와 함께 정리합니다.

핵심부터 요약할게요.

  1. 동기/비동기의 축: 작업 완료를 누가 챙기는가 — 호출한 쪽이 직접 챙기면 동기, 통지받으면 비동기
  2. 블로킹/논블로킹의 축: 호출했을 때 제어권을 바로 돌려주는가 — 안 돌려주면 블로킹, 바로 돌려주면 논블로킹
  3. 두 축은 독립이라 2×2 = 4가지 조합이 모두 존재한다
  4. 실무에서 자주 만나는 건 동기+블로킹, 비동기+논블로킹 두 가지

축 1: 동기 vs 비동기 — 완료를 누가 챙기나

동기(synchronous)는 작업의 완료 여부를 호출한 쪽이 직접 챙기는 방식입니다. 요청을 보냈으면 결과가 나올 때까지 흐름이 그 작업에 묶여요.

A 끝나면 B, B 끝나면 C. 순서가 보장됩니다.

비동기(asynchronous)는 요청만 던져 두고 다음 일을 하다가 완료되면 통지받는 방식입니다. 콜백, 클로저, async/await의 continuation이 전부 이 “통지 창구”예요(POSIX aio_read).

식당으로 비유해 볼까요. 동기는 주문하고 카운터 앞에서 음식이 나올 때까지 기다렸다 받는 겁니다.

비동기는 진동벨을 받아 들고 자리에서 딴짓하다가, 벨이 울리면 가지러 가는 거고요.


축 2: 블로킹 vs 논블로킹 — 제어권을 돌려주나

이번엔 관점이 다릅니다. 호출 당한 함수가 제어권을 바로 돌려주느냐가 기준이에요.

블로킹(blocking)은 호출하면 함수가 끝날 때까지 호출한 스레드가 묶이는 겁니다. 그동안 그 스레드는 아무것도 못 해요.

논블로킹(non-blocking)은 호출하면 일단 즉시 리턴합니다. 결과가 준비 안 됐으면 “아직 안 됐어요”라는 상태라도 돌려줘요(POSIX read).

동기/비동기가 “완료 관리 방식”이라면, 블로킹/논블로킹은 “대기 여부”입니다. 축이 다르죠.


2×2 조합 전부 살펴보기

동기·비동기와 블로킹·논블로킹 두 축이 만드는 2×2 조합 다이어그램
두 축은 독립이라 네 조합이 전부 존재합니다
조합 동작 대표 예
동기 + 블로킹 기다렸다가 결과 받아서 진행 일반적인 함수 호출, 기본 파일 read
동기 + 논블로킹 바로 리턴받고, 됐는지 반복 확인(폴링) 논블로킹 소켓 + 루프에서 계속 확인
비동기 + 블로킹 통지를 기다리는데 스레드는 묶임 비동기 API 걸어놓고 결과를 즉시 wait
비동기 + 논블로킹 던져두고 딴 일, 완료되면 통지 URLSession 콜백, async/await

동기+논블로킹이 낯설 텐데, “진동벨 없이 1분마다 카운터에 가서 나왔어요? 하고 묻는 손님”입니다.

제어권은 갖고 있지만 완료를 자기가 챙기니 동기죠.

비동기+블로킹은 사실상 손해 조합입니다. 비동기로 만들어 놓고 결과를 바로 기다려버리면 동기+블로킹과 다를 게 없는데 구조만 복잡해져요.

비동기 함수에 만나자마자 wait을 거는 코드가 이 사례입니다.


Swift에서 보면

GCD(Grand Central Dispatch) 시절의 sync/async는 사실 블로킹 여부에 가까운 이름입니다.

queue.sync는 그 클로저가 끝날 때까지 현재 스레드를 묶습니다(블로킹). queue.async는 던져놓고 바로 다음 줄로 넘어가죠(논블로킹).

async/await은 비동기+논블로킹을 동기 코드처럼 읽히게 만든 문법입니다.

let data = try await fetchImage() // 여기서 '중단'되지만
// 스레드는 묶이지 않고 다른 일을 하러 간다

await 지점에서 함수는 잠시 멈추지만 스레드는 풀려나 다른 작업을 처리합니다. 코드는 동기처럼 위에서 아래로 읽히는데 실제 동작은 논블로킹인 게 핵심이에요.

“메인 스레드를 안 막으면서도 콜백 지옥 없이 순서대로 쓴다”가 이 문법의 존재 이유입니다.

await 지점에서 함수만 멈추고 스레드는 계속 흐르는 도로 비유 일러스트
await 지점에서 함수는 멈추지만 스레드는 풀려나 다른 일을 합니다

면접 포인트

두 축의 기준을 한 문장씩으로 가르는 게 시작입니다.

“블로킹/논블로킹은 제어권을 바로 돌려주느냐의 문제이고, 동기/비동기는 완료를 호출자가 챙기느냐 통지받느냐의 문제입니다.”

그다음 “네 조합의 예를 들어보라”는 꼬리 질문에 위 표의 사례를 하나씩 대면 됩니다. 특히 동기+논블로킹(폴링)을 설명할 수 있으면 축을 제대로 이해했다는 증거가 돼요.


정리

  • 동기/비동기: 완료를 호출자가 직접 챙기면 동기, 통지받으면 비동기
  • 블로킹/논블로킹: 호출 시 제어권을 안 돌려주면 블로킹, 즉시 돌려주면 논블로킹
  • 두 축은 독립적이라 4가지 조합이 모두 존재한다
  • 동기+논블로킹은 폴링, 비동기+블로킹은 대부분 손해 보는 조합
  • GCD의 sync/async는 블로킹 여부에 가깝고, async/await은 비동기+논블로킹을 동기 코드처럼 읽히게 한 문법
  • 식당 비유: 카운터에서 기다리기(동기+블로킹), 1분마다 물어보기(동기+논블로킹), 진동벨(비동기+논블로킹)

출처 및 확인 기준

  • POSIX read — The Open Group · 표준·명세 원문 · 확인 2026-08-17 · 근거: 블로킹 I/O와 O_NONBLOCK 동작
  • POSIX aio_read — The Open Group · 표준·명세 원문 · 확인 2026-08-17 · 근거: 비동기 I/O 요청과 완료 모델
  • Dispatch — Apple · 공식 문서 · 확인 2026-08-17 · 근거: 큐 기반 동기·비동기 작업 제출