본문 바로가기
Study/ETC

Sync/Async & Block/Non-block: 개념과 차이점 쉽게 이해하기

by 코드콩 2025. 1. 17.
728x90
반응형

프로그래밍에서 자주 마주치는 개념인 '동기/비동기'와 '블로킹/논블로킹'은 종종 혼동되거나 같은 의미로 잘못 이해되곤 합니다. 이 글에서는 이 두 개념의 차이점과 각각의 특징을 명확히 알아보겠습니다.

1. 핵심 개념 정리

Sync vs Async (동기 vs 비동기)

동기(Synchronous)비동기(Asynchronous)는 프로그램이 작업을 처리하는 방식에 차이를 둡니다.

  • 동기(Synchronous): 작업이 순차적으로 진행됩니다. 하나의 작업이 끝난 후에야 다음 작업을 시작할 수 있습니다. 즉, 현재 작업이 완료될 때까지 다른 작업을 기다려야 합니다.
    public void syncExample() {
      task1();  // task1이 끝날 때까지 task2는 시작되지 않음
      task2();
    }
  • 비동기(Asynchronous): 작업을 시작한 후, 해당 작업이 완료되기를 기다리지 않고 다른 작업을 병렬로 처리할 수 있습니다. 즉, 작업이 끝나는 시간을 기다리지 않고 다른 일을 할 수 있습니다.
    public void asyncExample() {
      CompletableFuture.runAsync(() -> task1());  // task1을 비동기로 실행
      task2();  // task2는 task1이 끝날 때까지 기다리지 않고 실행
    }

Block vs Non-block (블로킹 vs 논블로킹)

블로킹(Blocking)논블로킹(Non-blocking)은 주로 I/O 작업이나 네트워크 요청에서 동작 방식에 차이를 둡니다.

  • 블로킹(Blocking): 작업을 요청한 후, 해당 작업이 완료될 때까지 코드 실행이 멈춥니다. 예를 들어, 파일을 읽거나 네트워크 요청을 할 때, 요청이 끝날 때까지 다른 코드가 실행되지 않습니다.
    public void blockingExample() {
      readFile();  // 파일 읽기를 완료할 때까지 기다림
      System.out.println("File read complete.");
    }
  • 논블로킹(Non-blocking): 요청을 보낸 후, 해당 작업이 완료되지 않아도 다른 작업을 계속해서 실행할 수 있습니다. I/O 작업이나 네트워크 요청이 끝날 때까지 기다리지 않고 다른 처리를 할 수 있습니다.
    public void nonBlockingExample() {
      readFileAsync();  // 파일 읽기를 비동기로 실행
      System.out.println("File read started, but not waiting.");
    }

2. 네 가지 조합의 이해

2.1 동기-블로킹 (Synchronous-Blocking)

// 전형적인 동기-블로킹 예제
public String readFile() {
    return Files.readString(Path.of("file.txt")); // 파일을 읽을 때까지 대기
}
  • 특징:
    • 작업이 완료될 때까지 대기
    • 다른 작업을 수행할 수 없음
  • 사용 사례:
    • 간단한 파일 입출력
    • JDBC를 사용한 데이터베이스 연동

2.2 동기-논블로킹 (Synchronous-Non-blocking)

// 동기-논블로킹 예제
while (!futureResult.isDone()) {
    // 다른 작업을 수행할 수 있음
    doSomethingElse();
}
  • 특징:
    • 작업 완료를 기다리지만, 그 동안 다른 일을 할 수 있음
    • 주기적으로 작업 완료 여부를 확인
  • 사용 사례:
    • Future/Promise 패턴
    • NIO의 Selector

2.3 비동기-블로킹 (Asynchronous-Blocking)

// 비동기-블로킹 예제
CompletableFuture.supplyAsync(() -> {
    return blockingOperation(); // 블로킹 발생
}).thenAccept(result -> {
    processResult(result);
});
  • 특징:
    • 비동기로 작업을 시작하지만, 내부적으로 블로킹이 발생
    • 일반적으로 피해야 할 패턴
  • 사용 사례:
    • 블로킹 API를 비동기 프레임워크에서 사용할 때

2.4 비동기-논블로킹 (Asynchronous-Non-blocking)

// 비동기-논블로킹 예제
webClient.get()
    .uri("/api/data")
    .retrieve()
    .bodyToMono(String.class)
    .subscribe(data -> {
        processData(data);  // 데이터가 준비되면 처리
    });
  • 특징:
    • 작업을 시작하고 바로 반환
    • 작업이 완료되면 콜백이나 이벤트로 처리
  • 사용 사례:
    • Spring WebFlux
    • Node.js의 비동기 IO
    • Reactive Programming

3. 실제 사용 예시

3.1 파일 처리 예시

// 동기-블로킹
String content = Files.readString(Path.of("file.txt"));

// 비동기-논블로킹
AsyncFileChannel.open(Path.of("file.txt"))
    .read(buffer, 0)
    .thenAccept(bytesRead -> {
        // 처리 로직
    });

3.2 네트워크 요청 예시

// 동기-블로킹 (RestTemplate)
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);

// 비동기-논블로킹 (WebClient)
webClient.get()
    .uri(url)
    .retrieve()
    .bodyToMono(String.class)
    .subscribe(response -> {
        // 처리 로직
    });

4. 선택 기준

4.1 동기-블로킹을 선택하는 경우

  • 간단한 로직이나 빠른 처리가 필요할 때
  • 순차적인 처리가 중요할 때
  • 코드의 가독성이 중요할 때

4.2 비동기-논블로킹을 선택하는 경우

  • 높은 동시성이 필요할 때
  • IO 작업이 많은 경우
  • 확장성이 중요한 경우

5. 성능 고려사항

5.1 리소스 사용

  • 동기-블로킹: 스레드 당 하나의 요청 처리
  • 비동기-논블로킹: 적은 수의 스레드로 많은 요청 처리 가능

5.2 메모리 사용

  • 동기-블로킹: 스레드 스택 메모리 사용
  • 비동기-논블로킹: 이벤트 루프와 콜백으로 메모리 효율적 사용

마치며

Sync/Async와 Block/Non-block은 서로 다른 개념이지만, 함께 사용될 때 프로그램의 효율성에 큰 영향을 미칩니다. 비동기와 논블로킹을 잘 활용하면, 멀티스레드 환경에서 더 빠르고 효율적인 시스템을 구축할 수 있습니다. 이 두 개념을 잘 이해하고, 상황에 맞게 적용하는 것이 중요합니다.

728x90
반응형