munjji 님의 블로그
[CS] 비동기 처리 본문
비동기를 이해하기 위해 가장 먼저 이해해야 할 건 동기(Synchronous)와 비동기(Asychronous)의 차이다.
동기 vs 비동기 처리
동기(Synchronous)란?
코드가 위에서 아래로 순서대로 실행되고, 앞 작업이 완전히 끝나야 다음 작업이 시작된다.
console.log("1. 커피 주문");
sleep(3000); // 3초 대기 (커피 만드는 중)
console.log("2. 커피 받음");
console.log("3. 자리 착석");
// 출력 순서: 1 → (3초 대기) → 2 → 3
직관적이고 코드 흐름을 따라가기 쉽다. 하지만 sleep(3000) 동안 프로그램 전체가 멈춰있다. 이 상태를 블로킹(Blocking) 이라고 한다.
왜 블로킹이 문제인가?
혼자 쓰는 프로그램이라면 사실 큰 문제가 없을 것이다. 문제는 서버처럼 여러 사람이 동시에 요청을 보내는 환경에서 발생한다.
사용자 A가 대용량 파일을 업로드하는 동안, 사용자 B가 단순히 "안녕하세요"라는 텍스트를 조회하는 요청을 보낸다고 가정해보면, 동기 서버라면 A의 업로드가 끝날 때까지 B는 무조건 기다려야 한다. A의 작업이 10초 걸린다면 B도 10초를 기다려야 한다.
비동기(ASynchronous)란?
오래 걸리는 작업을 기다리지 않고 일단 시작만 해놓고, 그 사이에 다른 일을 한다.
작업이 완료되면 그때 결과를 처리한다.
console.log("1. 커피 주문 (진동벨 받음)");
setTimeout(() => {
console.log("3. 커피 완성! 가져옴"); // 3초 후 실행
}, 3000);
console.log("2. 자리에 앉아서 핸드폰 봄"); // 기다리지 않고 바로 실행
// 출력 순서: 1 → 2 → (3초 후) → 3
`setTimeout`을 만나는 순간, "3초 뒤에 이 함수 실행해줘" 하고 맡겨두고 바로 다음 줄로 넘어갑니다. 3초 뒤에 콜백 함수가 호출된다.
JavaScript가 비동기를 처리하는 방법 — 이벤트 루프
JavaScript는 싱글 스레드 언어이다.
즉, 한 번에 하나의 작업만 처리할 수 있다. 그런데 어떻게 비동기가 가능할까?
바로 이벤트 루프(Event Loop) 덕분이다.
┌─────────────────────────────────┐
│ Call Stack │ ← 지금 실행 중인 코드
│ (한 번에 하나씩만 처리 가능) │
└────────────────┬────────────────┘
│
┌────────────────▼────────────────┐
│ Event Loop │ ← 계속 감시하는 루프
│ "콜 스택 비었어? 그럼 다음 꺼" │
└────────────────┬────────────────┘
│
┌────────────────▼────────────────┐
│ Callback Queue │ ← 완료된 비동기 작업들이 대기
│ (setTimeout, fetch 결과 등) │
└─────────────────────────────────┘
setTimeout이나 네트워크 요청 같은 작업은 브라우저/Node.js의 Web API가 대신 처리한다. 완료되면 결과를 Callback Queue에 넣고, 이벤트 루프가 Call Stack이 비어있을 때 꺼내 실행한다.
콜백 → Promise → async/await 진화 과정 (JS)
비동기를 다루는 문법이 시간이 지나면서 발전해왔다.
1단계: 콜백(Callback) — 초창기 방식
fetchUser(userId, function(user) {
fetchOrders(user.id, function(orders) {
fetchProducts(orders[0].id, function(product) {
console.log(product); // 3단계 중첩...
});
});
});
콜백 안에 콜백, 그 안에 또 콜백...
이걸 콜백 지옥(Callback Hell)이라고 부른다. 코드가 오른쪽으로 계속 들여쓰여지고, 에러 처리도 각 단계마다 따로 해야 해서 지저분해집니다.
2단계: Promise — ES6(2015)에서 등장
fetchUser(userId)
.then(user => fetchOrders(user.id))
.then(orders => fetchProducts(orders[0].id))
.then(product => console.log(product))
.catch(err => console.error(err)); // 에러는 여기서 한 번에 처리
.then()으로 체이닝하면서 훨씬 읽기 좋아졌다. 에러도 .catch() 하나로 모두 처리할 수 있게 됐다.
Promise는 세 가지 상태를 가진다.
1. pending(대기 중) 2. fulfilled(성공) 3. rejected(실패).
3단계: async/await — ES8(2017)에서 등장
async function getProduct(userId) {
try {
const user = await fetchUser(userId); // 완료될 때까지 여기서 대기
const orders = await fetchOrders(user.id); // 완료될 때까지 여기서 대기
const product = await fetchProducts(orders[0].id);
console.log(product);
} catch (err) {
console.error(err);
}
}
비동기 코드인데 마치 동기 코드처럼 읽힌다.
await는 Promise가 완료될 때까지 이 함수 안에서만 기다리고, 다른 코드 실행은 막지 않는다. 현재 가장 많이 쓰이는 방식이다!
동기비동기
| 동기 | 비동기 | |
| 실행 방식 | 순서대로, 기다림 | 기다리지 않고 계속 진행 |
| 블로킹 | 있음 | 없음 |
| 코드 복잡도 | 단순 | 상대적으로 복잡 |
| 적합한 상황 | 빠른 연산, 단순 스크립트 | DB 조회, 네트워크 요청, 파일 I/O |
비동기의 핵심은 기다리는 시간을 낭비하지 않는다는 것이다.
네트워크 요청, DB 조회, 파일 읽기처럼 I/O 작업은 CPU가 거의 일을 안 하고 그냥 기다리는 시간이 대부분이다. 그 대기 시간 동안 다른 요청을 처리하는 것이 비동기 처리의 핵심 가치이다.
비동기 처리가 쓰이는 대표적인 상황들 (Java)
1. 외부 API 동시 호출
여러 외부 서비스에서 데이터를 가져와야 할 때, 순차적으로 기다리지 않고 한꺼번에 요청한다.
// 날씨 앱 — 기온, 미세먼지, 자외선 지수를 각각 다른 API에서 가져옴
CompletableFuture<String> temperature = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "23°C";
});
CompletableFuture<String> dust = CompletableFuture.supplyAsync(() -> {
sleep(1500);
return "좋음";
});
CompletableFuture<String> uv = CompletableFuture.supplyAsync(() -> {
sleep(800);
return "보통";
});
// 셋 다 완료되면 조합
CompletableFuture.allOf(temperature, dust, uv).join();
System.out.println("기온: " + temperature.get());
System.out.println("미세먼지: " + dust.get());
System.out.println("자외선: " + uv.get());
// 순차: 1000 + 1500 + 800 = 3300ms
// 병렬: max(1000, 1500, 800) = 1500ms → 2배 이상 빠름
2. 이메일 / 알림 발송
가장 흔한 패턴입니다. 발송 결과를 굳이 기다릴 필요가 없는 작업입니다.
public void processOrder(Order order) {
// 핵심 로직 (동기 — 꼭 필요)
orderRepository.save(order);
paymentService.charge(order);
// 알림은 비동기로 — 실패해도 주문은 성공
CompletableFuture.runAsync(() -> emailService.sendOrderConfirm(order))
.exceptionally(ex -> {
log.error("이메일 발송 실패, 나중에 재시도 예정", ex);
return null;
});
CompletableFuture.runAsync(() -> smsService.send(order.getPhone(), "주문 완료!"));
CompletableFuture.runAsync(() -> pushService.send(order.getUserId(), "배송 준비 중"));
// 이메일/SMS/푸시 발송을 기다리지 않고 바로 응답
return OrderResponse.success(order.getId());
}
3. 대용량 데이터 배치 처리
수천 건의 데이터를 처리할 때 순서보다 속도가 중요한 경우이다.
List<Integer> userIds = List.of(1, 2, 3, /* ... */ 10000);
// 각 유저마다 리포트 생성 작업을 비동기로 제출
List<CompletableFuture<Void>> tasks = userIds.stream()
.map(userId -> CompletableFuture.runAsync(() -> {
Report report = generateReport(userId); // 유저별 리포트 생성
reportRepository.save(report);
}, executor))
.collect(Collectors.toList());
// 전체 완료 대기
CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0])).join();
System.out.println("전체 리포트 생성 완료");
// 순차: 10000명 × 1초 = 약 2.7시간
// 병렬(스레드 10개): 약 17분으로 단축
4. 타임아웃 처리
외부 서비스가 느릴 때 무한정 기다리지 않고, 일정 시간 초과 시 기본값을 반환한다.
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> externalApiCall()) // 느린 외부 API
.orTimeout(3, TimeUnit.SECONDS) // 3초 초과 시 예외 발생
.exceptionally(ex -> "기본값"); // 타임아웃이면 기본값 반환
String result = future.get();
System.out.println("결과: " + result); // 3초 넘으면 "기본값"
5. 캐시 미스 시 비동기 갱신
캐시가 없을 때 DB를 조회하고, 조회하는 동안 다른 요청도 처리한다.
public CompletableFuture<User> getUser(int userId) {
User cached = cache.get(userId);
if (cached != null) {
return CompletableFuture.completedFuture(cached); // 즉시 반환
}
// 캐시 미스 → DB 비동기 조회 후 캐시에 저장
return CompletableFuture.supplyAsync(() -> {
User user = db.findById(userId);
cache.put(userId, user);
return user;
}, executor);
}
6. 결과가 오는 순서대로 처리 — anyOf
여러 소스 중 가장 먼저 응답하는 것을 사용하고 싶을 때
// 같은 데이터를 여러 서버에 동시 요청, 가장 빠른 응답 사용
CompletableFuture<Object> fastest = CompletableFuture.anyOf(
CompletableFuture.supplyAsync(() -> { sleep(3000); return "서버A 응답"; }),
CompletableFuture.supplyAsync(() -> { sleep(1000); return "서버B 응답"; }),
CompletableFuture.supplyAsync(() -> { sleep(2000); return "서버C 응답"; })
);
System.out.println(fastest.get()); // "서버B 응답" (가장 빠름)'CS' 카테고리의 다른 글
| [정보처리기사] 5과목. 정보시스템 구축 관리 (0) | 2026.03.13 |
|---|---|
| [정보처리기사] 3과목. 데이터베이스 구축 (0) | 2026.03.13 |
| [정보처리기사] 2과목. 소프트웨어 개발 (0) | 2026.03.12 |
| [정보처리기사] 1과목. 소프트웨어 설계 (0) | 2026.03.11 |
| [CS] 메시지 큐 (0) | 2026.03.06 |