비동기
고친 사람 github-actions[bot]
비동기는 상대의 시간에 맞춰 기다리지 않는다는 뜻입니다. 이 말은 분야마다 가리키는 대상이 다릅니다. 프로그램 코드에서는 일을 맡겨 두고 끝나기 전에 다음 일로 넘어가는 호출을 가리킵니다. 분산 시스템 이론에서는 메시지가 언제 도착할지 아무 약속이 없는 네트워크를 가리킵니다.
쉽고 빠른 이해
무슨 일을 하나 — 일이 끝나기를 멈춰 서서 기다리지 않게 합니다. 서버가 데이터베이스에 조회를 맡겨 두고 그사이 다른 요청을 받는 것이 비동기입니다. 분산 시스템 이야기에서는 뜻이 바뀝니다. 메시지가 얼마나 늦게 올지 모르는 네트워크를 비동기라고 부릅니다.
왜 이렇게 하나 — 기다리는 동안 스레드는 아무 일도 못 합니다. 요청이 몰리면 멈춰 기다리는 스레드가 쌓입니다. 그러면 새 요청을 받을 스레드가 모자랍니다. 분산 시스템 쪽 뜻은 현실의 네트워크가 도착 시간을 약속하지 않아서 씁니다.
어떻게 도나 (호출의 비동기)
- 일을 맡기고 바로 돌아옵니다
- 맡긴 일이 도는 동안 다른 일을 합니다
- 일이 끝나면 결과를 따로 전달받습니다
대가 — 코드에 적힌 순서와 도는 순서가 달라져 흐름을 따라 읽기 어렵습니다. 그래서 기다림이 짧은 계산은 비동기로 부를 까닭이 없습니다. 분산 시스템 쪽에서는 답이 없는 서버가 늦는 것인지 멈춘 것인지 가를 수 없습니다.
상세
비동기는 호출·운영체제 사건·분산 시스템 모형 셋에서 뜻이 갈립니다. 이 절은 백엔드 코드에서 제일 자주 만나는 호출의 비동기를 중심으로 봅니다.
비동기가 가리키는 세 가지
비동기는 반대말부터 잡으면 빠릅니다. 동기(synchronous)는 시간을 맞춘다는 뜻입니다. 한쪽이 다른 쪽의 진행에 맞춰 멈추고 기다립니다. 비동기(asynchronous)는 그 맞춤을 하지 않는다는 뜻입니다.
무엇이 무엇에 시간을 안 맞추는지는 맥락마다 다릅니다.
| 맥락 | 비동기가 뜻하는 것 | 동기가 뜻하는 것 |
|---|---|---|
| 프로그램의 호출 | 일을 맡긴 쪽이 끝나기를 기다리지 않고 다음 일로 넘어간다 | 맡긴 쪽이 일이 끝날 때까지 멈춰 기다린다 |
| 운영체제의 사건 | 프로그램이 지금 하는 일과 상관없이 바깥에서 일어난다 | 프로그램이 방금 한 동작 때문에 일어난다 |
| 분산 시스템의 모형 | 메시지가 도착하는 시간과 서버가 처리하는 시간에 상한이 없다 | 두 시간에 상한이 있고 모두가 그 값을 안다 |
세 뜻 모두 「상대의 시간에 묶이지 않는다」는 점은 같습니다. 묶이지 않는 대상이 다를 뿐입니다. 그래서 한 분야에서 익힌 뜻으로 다른 분야의 글을 읽으면 엉뚱하게 읽힙니다.
동시성과 병렬성을 이야기할 때의 비동기는 표의 첫째 줄인 호출입니다. 두 낱말은 여러 일을 한꺼번에 진행하는 방식을 말합니다. 비동기와 어떻게 다른지는 아래 「동시성·병렬성과의 차이」 소절에서 봅니다.
합의 알고리즘은 여러 서버가 값 하나에 뜻을 모으는 절차입니다. Paxos 같은 합의 알고리즘에서 말하는 비동기는 표의 셋째 줄인 모형입니다. 이쪽의 비동기는 코드를 짜는 방식 이야기가 아닙니다.
호출의 비동기
빨래를 하는 방법은 둘입니다. 코인 세탁기 앞에 앉아 다 돌 때까지 기다릴 수 있습니다. 세탁소에 맡기고 번호표를 받은 뒤 다른 볼일을 보러 갈 수도 있습니다. 앞쪽이 동기이고 뒤쪽이 비동기입니다.
프로그램에서 기다리는 주체는 스레드입니다. 스레드는 프로그램 안에서 코드를 한 줄씩 따라 도는 실행 흐름 하나입니다. 동기 호출에서는 스레드가 결과가 나올 때까지 그 줄에 멈춰 있습니다. 이렇게 멈춰 기다리는 것을 블로킹이라고 부릅니다.
비동기 호출은 일을 맡기고 바로 돌아옵니다. 스레드는 다음 줄로 넘어가 다른 일을 합니다. 맡긴 일이 끝나면 그 결과가 나중에 따로 전달됩니다.
sequenceDiagram
participant 부른쪽 as 부른 쪽
participant 맡은쪽 as 맡은 쪽
부른쪽->>맡은쪽: 일을 맡긴다
맡은쪽-->>부른쪽: 받았다 · 바로 돌아온다
Note over 부른쪽: 다른 일을 한다
Note over 맡은쪽: 맡은 일을 한다
맡은쪽-->>부른쪽: 끝났다 · 결과를 전달한다
그림의 가운데 두 줄이 비동기의 쓸모입니다. 부른 쪽과 맡은 쪽이 같은 시간에 각자 일을 합니다. 동기 호출이었다면 부른 쪽은 그 시간 내내 멈춰 있습니다.
백엔드 서버에서 이 차이는 스레드 수로 드러납니다. 데이터베이스 조회는 서버 안의 계산보다 훨씬 오래 걸립니다. 자바에서 JDBC(Java Database Connectivity)의 executeQuery 로 조회하면 결과가 올 때까지 스레드 하나가 그 시간 내내 멈춰 있습니다. 요청이 몰리면 멈춘 스레드가 쌓여 새 요청을 받을 스레드가 모자랍니다.
비동기로 부르면 스레드 하나가 조회를 맡겨 두고 그사이 다른 요청을 받습니다. 같은 수의 스레드로 더 많은 요청을 동시에 붙잡고 있을 수 있습니다.
반대로 금방 끝나는 계산은 비동기로 부를 까닭이 적습니다. 기다림이 짧아 아낄 시간이 없습니다. 맡기고 돌려받는 절차만 늘어납니다.
결과를 돌려받는 방법
맡긴 일의 결과는 나중에 옵니다. 그 결과를 어디서 받느냐에 따라 방법이 셋으로 나뉩니다.
| 방법 | 결과가 오는 모습 |
|---|---|
| 콜백 | 일을 맡길 때 「끝나면 이 함수를 불러 달라」며 함수를 함께 넘긴다 |
| 퓨처 · Promise | 일을 맡기면 나중에 결과가 채워질 객체를 바로 받는다. 결과는 그 객체에서 꺼낸다 |
| async-await | 비동기 호출을 동기 코드처럼 위에서 아래로 적는다. 기다리는 동안 스레드는 다른 일로 넘어간다 |
셋은 결과를 쓰는 코드를 어디에 적느냐가 다릅니다. 콜백은 그 코드를 따로 떨어진 함수 안에 적습니다. async-await 는 그 코드를 호출 바로 아랫줄에 적게 해 줍니다. 읽는 순서와 도는 순서를 비슷하게 맞추려는 것입니다.
적힌 순서와 도는 순서
순서가 어떻게 바뀌는지는 자바스크립트 코드 몇 줄로 볼 수 있습니다. 오른쪽 주석은 그 줄의 글자가 몇 번째로 찍히는지입니다.
const log = console.log;
log("A"); // 1번째
setTimeout(() => log("B")); // 3번째
log("C"); // 2번째
setTimeout 은 함수를 나중에 부르도록 맡기고 바로 돌아옵니다. 맡긴 함수는 지금 돌고 있는 코드가 다 끝난 뒤에 불립니다. 그래서 코드에서는 B 가 C 보다 위에 있는데도 C 가 먼저 찍힙니다.
코드에 적힌 순서와 도는 순서가 달라지는 것이 비동기의 대가입니다. 결과를 쓰는 코드가 호출한 코드와 떨어지면 흐름을 따라 읽기 어려워집니다. 맡긴 일에서 난 오류도 호출한 줄이 아니라 결과를 받는 쪽에서 드러납니다.
서비스 사이의 비동기
같은 뜻은 서비스와 서비스 사이에도 이어집니다. 메시지 큐는 보낸 메시지를 받는 쪽이 가져갈 때까지 쌓아 두는 중간 저장소입니다. 한 서비스가 일을 메시지 큐에 넣어 두고 답을 기다리지 않으면 비동기 통신이라고 부릅니다.
받는 서비스가 잠깐 멈춰 있어도 보내는 서비스는 계속 일합니다. 메시지는 큐에 남아 있다가 받는 쪽이 돌아오면 처리됩니다.
동시성·병렬성과의 차이
비동기 호출이 있다고 해서 곧 동시성은 아닙니다. 동시성은 여러 일이 겹친 시간 구간 안에서 함께 진행되는 성질입니다. 비동기 호출을 해 놓고 부른 쪽이 결과만 기다리면 겹치는 구간이 없습니다. 부른 쪽이 그사이 다른 일을 진행해야 두 일이 겹칩니다.
비동기는 병렬성과도 다릅니다. 병렬성은 여러 계산이 여러 코어에서 같은 순간에 도는 것입니다. 비동기 호출은 코어 하나와 스레드 하나로도 할 수 있습니다. 앞의 자바스크립트 코드가 스레드 하나로 도는 예입니다.
논블로킹과 가르는 기준
논블로킹 호출도 기다리지 않고 바로 돌아옵니다. 그래서 비동기와 자주 섞여 쓰입니다. 둘은 일을 누가 끝까지 챙기느냐가 다릅니다.
논블로킹 호출은 지금 당장 할 수 있는 만큼만 하고 돌아옵니다. 논블로킹 모드로 연 소켓에서 read 를 부르면, 읽을 데이터가 아직 없을 때 「아직 없다」를 돌려줍니다. 준비됐는지 다시 물어보는 일은 부른 쪽의 몫입니다.
비동기 호출은 일 전체를 맡기고 돌아옵니다. 부른 쪽은 다시 묻지 않습니다. aio_read 같은 비동기 파일 읽기라면 데이터가 부른 쪽이 내준 메모리에 다 채워진 뒤에 끝났다는 알림이 옵니다.
| 블로킹 호출 | 논블로킹 호출 | 비동기 호출 | |
|---|---|---|---|
| 호출이 돌아오는 때 | 일이 끝난 뒤 | 바로 | 바로 |
| 돌아올 때 한 일 | 전부 | 지금 할 수 있는 만큼 | 맡기기만 했다 |
| 끝났는지 챙기는 쪽 | 없다 | 부른 쪽이 다시 묻는다 | 맡은 쪽이 알려 온다 |
표의 마지막 줄이 둘을 가르는 기준입니다.
운영체제의 비동기 사건
운영체제에서 비동기는 사건이 언제 일어나느냐를 말합니다. 프로그램이 지금 무엇을 하든 상관없이 바깥에서 일어나는 사건이 비동기 사건입니다.
시그널이 대표입니다. 시그널은 운영체제가 실행 중인 프로그램에 보내는 짧은 알림입니다. 터미널에서 Ctrl+C 를 누르면 프로그램에 멈추라는 시그널이 갑니다. 프로그램이 어느 줄을 돌고 있었든 상관없이 오는 이런 시그널이 비동기 시그널입니다.
프로그램이 잘못된 메모리 주소를 건드려도 시그널이 옵니다. 이쪽은 원인이 된 줄을 짚을 수 있으므로 동기 시그널입니다. 동기냐 비동기냐는 시그널의 종류가 아니라 시그널이 생긴 방식이 정합니다.
하드웨어가 프로세서에 보내는 인터럽트도 같은 뜻의 비동기 사건입니다. 비동기 사건은 프로그램의 어느 두 줄 사이에든 끼어들 수 있습니다.
분산 시스템의 비동기
분산 시스템을 따질 때는 먼저 네트워크와 서버가 어떻게 움직인다고 칠지 정합니다. 이 가정 묶음을 시스템 모형이라고 부릅니다. 모형을 정해야 「이 알고리즘은 이런 상황에서 옳다」를 증명할 수 있습니다.
동기 모형은 두 시간에 상한이 있다고 칩니다. 메시지가 도착하는 데 걸리는 시간과 서버가 한 단계를 처리하는 시간입니다. 모두가 그 상한 값을 압니다.
비동기 모형은 그 상한이 없다고 칩니다. 메시지는 얼마든지 늦게 도착할 수 있습니다. 서버도 얼마든지 느리게 돌 수 있습니다.
늦는 서버와 멈춘 서버
비동기 모형에서 서버 A 가 서버 B 에 요청을 보내고 답을 못 받았다고 합시다. A 가 보기에 원인은 아래 셋 중 하나입니다.
flowchart TD
Q["A 가 보낸 요청에 답이 없다"]
Q --> M["메시지가 아직 가는 중이다"]
Q --> S["B 가 느리게 돌고 있다"]
Q --> D["B 가 멈췄다"]
M --> V["A 에게는 셋이 똑같이 보인다"]
S --> V
D --> V
상한이 없으니 얼마를 기다려야 멈췄다고 확신할지 정할 수 없습니다. 타임아웃은 정해 둔 시간이 지나면 기다림을 끊는 장치입니다. 비동기 모형에서는 타임아웃이 지나도 B 가 멈췄다고 단정하지 못합니다.
이 문제가 가장 크게 걸리는 곳이 합의입니다. 합의는 여러 서버가 값 하나에 뜻을 모으는 일입니다. 복제된 서버들이 다음에 적을 기록 하나를 똑같이 고르는 것이 그 예입니다.
FLP 불가능성은 비동기 모형에서 서버 한 대만 멈출 수 있어도 합의를 언제나 끝내는 알고리즘은 없다는 증명입니다. 늦는 서버와 멈춘 서버를 못 가르는 문제가 이 증명의 뿌리입니다. FLP 는 이 증명을 낸 세 사람 Michael Fischer · Nancy Lynch · Michael Paterson 의 성에서 딴 머리글자입니다.
그래서 비동기 모형을 가정한 합의 알고리즘은 약속 둘을 떼어 놓습니다. 첫째 약속은 안전성입니다. 틀린 값을 고르지 않는다는 뜻입니다. 이 약속은 메시지가 아무리 늦어도 지킵니다.
둘째 약속은 활성입니다. 결정을 언젠가 끝낸다는 뜻입니다. 이 약속은 네트워크가 한동안 제때 메시지를 나를 때에만 지킵니다. Paxos 가 이렇게 짜여 있습니다.
동기와 비동기 사이에는 부분 동기 모형도 있습니다. 상한은 있지만 그 값을 모른다고 칩니다. 또는 상한이 한동안 안 지켜지다가 어느 때부터 지켜진다고 칩니다. 실무의 합의 알고리즘은 대개 안전성을 비동기 모형에서, 활성을 부분 동기 모형에서 따집니다.
이 비동기는 코드를 짜는 방식과 상관이 없습니다. 동기 호출로만 짠 서버들도 도착 시간을 약속하지 않는 네트워크로 이어져 있으면 비동기 모형 위에 있습니다.
관련 항목
비동기와 맞세워지는 대립 개념
동기 · 동기 호출 · 블로킹 · 블로킹 호출 · 동기 입출력
비동기 호출의 결과를 돌려받는 수단
콜백 · 퓨처 · Promise · async-await · 이벤트 루프 · 코루틴
비동기를 앞에 붙여 부르는 기술
비동기 프로그래밍 · 비동기 입출력 · 비동기 메시징 · 비동기 복제 · 비동기 커밋 · 비동기 처리
비동기와 헷갈리는 이웃 개념
서비스 사이를 비동기로 잇는 수단
메시지 큐 · 발행-구독 · 작업 큐 · 이벤트 기반 모델
프로그램에 비동기로 끼어드는 운영체제 사건
시그널 · 인터럽트 · 시그널 핸들러
비동기 네트워크를 가정하는 합의 이론
비동기 네트워크 · 부분 동기 모델 · FLP 불가능성 · 합의 · Paxos · Raft · 안전성 · 활성 · 타임아웃 · 장애 감지기
다른 이름: asynchronous · async · 비동기식 · 비동기성