동기(Synchronous)와 비동기(Asynchronous)는 프로그램 실행 방식에서 작업의 처리 순서와 제어 흐름이 어떻게 이루어지는지를 정의하는 개념입니다. 이 개념은 주로 프로세스, 쓰레드, 함수 호출, 네트워크 요청 등에서 사용됩니다.
1. 동기(Synchronous)
- 정의: 작업을 순차적으로 실행하며, 하나의 작업이 완료될 때까지 대기한 후 다음 작업을 실행하는 방식.
- 특징:
- 작업의 결과를 기다렸다가 다음 작업을 실행함.
- 실행 순서가 직관적이며, 디버깅과 구현이 상대적으로 쉬움.
- 작업 완료 전까지 다음 작업으로 진행하지 않으므로 성능이 느려질 수 있음.
동작 방식
- 요청 → 대기 → 결과 반환 → 다음 작업.
- 작업 A가 끝날 때까지 작업 B는 시작되지 않음.
비유
- “음식점에서 주문한 음식을 모두 조리한 뒤에야 고객에게 서빙하는 방식.”
- 요리사는 한 고객의 음식을 완성하기 전까지 다음 고객을 받지 않음.
장점
- 코드의 흐름이 이해하기 쉬움.
- 작업이 순차적으로 처리되므로 데이터 처리의 순서를 보장.
단점
- 작업이 오래 걸리면 시스템 전체가 대기 상태가 됨.
- 리소스 사용 효율성이 낮음.
예제 (Java):
public void synchronousExample() {
System.out.println("작업 A 시작");
System.out.println("작업 A 완료");
System.out.println("작업 B 시작");
System.out.println("작업 B 완료");
}
실행 결과:
작업 A 시작
작업 A 완료
작업 B 시작
작업 B 완료
2. 비동기(Asynchronous)
- 정의: 작업을 병렬적으로 실행하며, 특정 작업이 완료될 때까지 대기하지 않고 다른 작업을 수행하는 방식.
- 특징:
- 작업의 완료 여부와 관계없이 다음 작업을 바로 시작함.
- 비동기적으로 실행된 작업의 결과는 나중에 콜백(Callback) 함수나 이벤트를 통해 처리.
- CPU와 자원을 효율적으로 사용할 수 있어 성능이 좋아짐.
동작 방식
- 요청 → 다음 작업 실행 → 결과 반환(콜백 또는 이벤트).
- 작업 A와 작업 B가 동시에 진행될 수 있음.
비유
- “음식점에서 여러 주문을 한 번에 받아 놓고, 준비되는 대로 고객에게 서빙하는 방식.”
- 요리사는 각 고객의 요리를 병렬적으로 준비하며, 완료된 요리부터 전달함.
장점
- 시스템 성능과 처리 속도 향상.
- 리소스를 효율적으로 사용할 수 있음.
단점
- 코드 흐름이 복잡해짐.
- 디버깅과 예외 처리가 어려움.
예제 (Java, 비동기 작업):
import java.util.concurrent.CompletableFuture;
public void asynchronousExample() {
System.out.println("작업 A 시작");
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000); // 작업 A 실행 중
System.out.println("작업 A 완료");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("작업 B 시작");
System.out.println("작업 B 완료");
}
실행 결과:
작업 A 시작
작업 B 시작
작업 B 완료
작업 A 완료 (2초 후)
3. 동기와 비동기의 차이점
구분 | 동기 (Synchronous) | 비동기 (Asynchronous) |
작업 방식 | 작업이 완료될 때까지 대기 후 다음 작업 실행 | 작업 완료 여부와 관계없이 즉시 다음 작업 실행 |
속도 | 느릴 수 있음 | 빠르고 효율적 |
제어 흐름 | 순차적, 직관적 | 병렬적, 복잡한 흐름 |
사용 사례 | 데이터베이스 트랜잭션, 파일 I/O 처리 | 네트워크 요청, UI 이벤트 처리 |
4. 주요 사용 사례
동기 사용 사례
- 데이터베이스 트랜잭션: 데이터 처리 순서를 보장해야 함.
- 파일 읽기/쓰기: 간단한 파일 작업에서 비동기가 불필요할 때.
비동기 사용 사례
- 네트워크 요청: HTTP 요청 응답을 기다리지 않고 다른 작업 수행.
- UI 이벤트 처리: 버튼 클릭, 스크롤 등.
- 대용량 데이터 처리: 백그라운드에서 처리해야 할 작업(예: 로그 분석).
5. 추가 개념: 동기 비동기와 블로킹/논블로킹
동기/비동기와 블로킹/논블로킹은 서로 다른 개념이지만, 자주 함께 논의됨.
블로킹 (Blocking)
- 호출된 작업이 완료될 때까지 호출자가 대기.
- 비유: “전화가 연결될 때까지 수화기를 들고 기다리는 상황.”
논블로킹 (Non-Blocking)
- 호출된 작업이 완료되지 않아도 즉시 반환하며, 작업이 완료되면 콜백으로 결과를 처리.
- 비유: “전화가 연결되기를 기다리지 않고, 연결되면 알림을 받는 상황.”
차이점 정리
구분 | 동기 | 비동기 |
블로킹 | 작업이 완료될 때까지 대기 | 비동기로 요청 후 결과를 받기 전까지 대기 |
논블로킹 | 작업이 완료될 때까지 대기하지 않음 | 요청 후 작업과 무관하게 다른 작업 수행 |
요약
- 동기(Synchronous)는 작업 완료를 기다렸다가 다음 작업을 수행.
- 비동기(Asynchronous)는 작업 완료를 기다리지 않고 병렬적으로 작업을 수행.
- 동기는 간단하지만 느릴 수 있음, 비동기는 복잡하지만 효율적.
- 비동기 프로그래밍은 주로 네트워크 요청, 대량 데이터 처리, UI 이벤트 등에 사용됨.
Q1: 비동기 처리에서 발생할 수 있는 콜백 지옥(Callback Hell) 문제를 해결하기 위한 방법은 무엇인가?
1. CompletableFuture 활용
- CompletableFuture는 Java 8에서 도입된 비동기 작업을 다루는 클래스입니다. 작업 체이닝(thenApply, thenCompose)을 사용해 콜백 중첩을 방지할 수 있다.
- 예제:
CompletableFuture.supplyAsync(() -> {
// 작업 A
return "작업 A 결과";
}).thenApply(resultA -> {
// 작업 B (A 결과 활용)
return resultA + " + 작업 B 결과";
}).thenAccept(finalResult -> {
// 최종 결과 처리
System.out.println(finalResult);
});
2. @Async 활용
- Spring에서 제공하는 @Async 애노테이션을 사용하면 비동기 작업을 간단히 관리할 수 있습니다. 반환값으로 CompletableFuture를 사용해 체이닝이 가능.
- 예제:
@Async
public CompletableFuture<String> asyncMethodA() {
return CompletableFuture.supplyAsync(() -> "작업 A 결과");
}
@Async
public CompletableFuture<String> asyncMethodB(String input) {
return CompletableFuture.supplyAsync(() -> input + " + 작업 B 결과");
}
public void execute() {
asyncMethodA().thenCompose(this::asyncMethodB)
.thenAccept(finalResult -> System.out.println(finalResult));
}
3. Reactor와 같은 라이브러리 사용
- Reactor (Spring WebFlux)는 비동기 스트림을 처리하기 위해 설계된 라이브러리입니다. Mono와 Flux를 사용하면 비동기 작업의 체이닝이 간결해진다.
- 예제:
Mono.just("작업 A 결과")
.map(resultA -> resultA + " + 작업 B 결과")
.doOnNext(finalResult -> System.out.println(finalResult))
.subscribe();
Q2: 동기와 비동기를 사용해야 할 상황을 더 구체적인 사례로 구분하여 설명.
동기를 사용해야 하는 경우
- 데이터의 순서와 무결성이 중요한 경우
- 예제: 데이터베이스 트랜잭션.
- 주문 생성 후 결제 데이터를 기록하는 과정에서 순서가 보장되어야 함.
- 예: Order를 저장한 후, Payment 테이블에 결제 정보를 저장.
- 사용 이유: 데이터 불일치가 발생하면 심각한 문제를 유발할 수 있으므로 동기 처리가 적합.
- 단일 작업이 오래 걸리지 않는 경우
- 예제: 단순한 파일 읽기/쓰기.
- 로그 파일을 저장하는 작업.
- 사용 이유: 간단한 작업에서는 비동기 처리가 오히려 복잡성을 증가시킬 수 있음.
비동기를 사용해야 하는 경우
- I/O 요청이 많은 경우
- 예제: REST API 호출이나 데이터베이스에서 대량의 데이터를 조회.
- 예: 외부 API를 호출해 환율 정보를 가져오는 작업.
- 사용 이유: 네트워크 요청은 대기 시간이 길기 때문에, 비동기로 처리해 리소스를 효율적으로 사용.
- 백그라운드 작업
- 예제: 이메일 발송, 이미지 처리.
- 사용자 요청에 응답한 후, 이메일 발송은 백그라운드에서 처리.
- 사용 이유: 사용자 경험을 향상시키고, 비동기적으로 작업을 완료.
- 다중 사용자 요청 처리
- 예제: 웹 서버의 요청 처리.
- 서버가 많은 요청을 병렬로 처리할 때 비동기가 효율적.
- 사용 이유: 각 요청이 비동기로 처리되면 시스템의 처리량이 증가.
Q3: 비동기 처리를 지원하는 Java의 CompletableFuture와 Spring의 @Async의 차이점은 무엇인가?
항목 | CompletableFuture | Spring @Async |
정의 | Java 8에서 제공하는 표준 비동기 API | Spring에서 제공하는 비동기 작업을 간단히 처리하는 애노테이션 |
스레드 풀 관리 | Executor를 명시적으로 설정 가능 | Spring이 자동으로 스레드 풀을 관리 |
사용 방식 | 체이닝(thenApply, thenCompose) 등을 통해 작업 조합 가능 | 비동기 메서드 호출 시 작업을 분리하여 실행 |
복잡한 작업 처리 | 복잡한 비동기 로직에 적합 | 상대적으로 간단한 비동기 작업에 적합 |
의존성 | 순수 Java 환경에서 동작 | Spring Context가 필요 |
예제 비교
1. CompletableFuture
• 사용 방식:
CompletableFuture.supplyAsync(() -> "작업 A 결과")
.thenApply(resultA -> resultA + " + 작업 B 결과")
.thenAccept(System.out::println);
• 사용 사례:
• 순수 Java 환경에서 비동기 작업을 체이닝하거나 병렬 작업이 필요할 때.
2. Spring @Async
• 사용 방식:
@Async
public CompletableFuture<String> asyncMethodA() {
return CompletableFuture.supplyAsync(() -> "작업 A 결과");
}
public void execute() {
asyncMethodA().thenAccept(System.out::println);
}
• 사용 사례:
• Spring 기반 애플리케이션에서 간단히 비동기 작업을 구현하고, 스레드 관리까지 Spring에 맡기고 싶을 때.
요약
1. 콜백 지옥 문제는 CompletableFuture, @Async, 또는 Reactor를 사용해 해결.
2. 동기는 데이터 순서 보장이 필요할 때, 비동기는 성능 최적화가 필요한 I/O 작업 및 병렬 처리가 필요한 상황에서 사용.
3. CompletableFuture는 복잡한 비동기 로직에 적합하며, @Async는 Spring 환경에서 간단한 비동기 작업에 적합.
출처 : ChatGPT
'CS' 카테고리의 다른 글
git branch 별 차이 (dev, origin dev, origin/dev) (0) | 2025.02.11 |
---|---|
소프트딜리트 Soft Delete, 하드딜리트 Hard Delete (1) | 2025.02.08 |
JMeter 성능테스트 (1) | 2025.02.04 |
DBMS, RDBMS, RDB (3) | 2025.02.03 |
Redis 레디스 (0) | 2025.02.01 |