개요
Synchronous & Asynchronous | Blocking & Non-Blocking의 개념과 실제로 어떻게 활용하는지, 네 개념의 상관관계는 어떻게되는지 정리하는 포스팅이다.
Synchronous
Sync 프로세스들은 서로 실행 시점에 따라 순서를 가지고, 순차적으로 실행이 된다.
아래와 같은 Java 예제 코드와 Synchronous한 동작 과정은 다음과 같다.
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void introduce() {
System.out.println("Hello, my name is " + name + ".");
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person("John");
person.introduce();
System.out.println("Main class continues...");
}
}
1. main 함수의 실행
2. person 클래스의 인스턴스를 생성
3. 호출된 introduce 메소드가 종료될 때까지 main은 대기
4. introduce 메소드가 종료되면, main 함수의 나머지 내용을 수행
main 클래스와 person 클래스는 호출되는 순서와 그 종료 시점의 영향을 받는다.
자주 사용하는 java나 python은 기본적으로 이 동작방식을 통해 메소드나 작업 호출이 순차적으로 구현 및 실행된다.
Asynchronous
Synchronous의 Anti- 즉, 비동기다.
동기적 프로그래밍과 반대로 두개 이상의 프로세스가 서로의 동작 시점 및 순서에 영향을 받지않는다.
앞의 예제를 활용해보면 main 함수가 person의 메소드를 호출했다 하더라도,
main은 person 메소드의 수행 여부와 상관없이 자기의 일을 묵묵히 해 나간다.
이를 통해서 main은 synchronous할 때보다 훨씬 빠르게 작업을 수행하고 끝낼 수 있다.
Asynchronous한 구현방식은 이처럼 효율적이고 반응성이 높은 구현을 할 수 있다는 장점이 있다.
Asynchronous는 항상 이상적일까?
다양한 문제상황
프로세스 A는 프로세스 B를 호출하고 이들이 비동기적으로 실행된다고 가정하자.
- A와 B는 독립적으로 서로 다른 쓰레드에 의해 실행 및 종료되기 때문에 순차적인 추적이 불가능하다.
- 비동기 작업은 여러 단계와 대기 시간이 포함될 수 있기 때문에 동기 작업보다 완료하는 데 걸리는 시간을 측정하는 것이 어려울 수 있다.
- 비동기에 대한 오류/예외를 처리하는 것은 오류가 비동기적으로 전파될 수 있기 때문에 까다롭다.
- 중요한 트랜잭션이 걸려있다면, 롤백하는 상황에서 어떻게 처리할 것인지 곤란해진다. (트래킹이 힘들기 때문)
Asynchronous 단점 해결 방안
CompatableFuture
비동기의 주요 문제점인 콜백 지옥을 해결한다.
콜백 지옥은 비동기 작업이 연속적으로 발생하고 응답을 기다리는 중첩된 콜백 함수들로 인해 유지보수성이 저하되는 문제점이다. CompletableFuture는 이를 해결하기 위해 메서드 체이닝을 통해 비동기 작업의 순차적인 처리를 간결하게 표현할 수 있다.
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
// 비동기 작업을 수행
return 42;
}).thenApply(result -> {
// 이전 작업의 결과를 가지고 추가적인 작업을 수행
return result * 2;
}).thenApply(result -> {
// 또 다른 작업을 수행
return result + 10;
});
future.thenAccept(result -> {
System.out.println("결과: " + result);
});
자세한 내용은 JAVA/CompatableFuture 게시물에 정리해서 여기에 첨부할 예정이다!
Kafka, RabbitMQ, AWS SQS 등의 메시징 큐
Kafka는 대용량의 실시간 데이터 스트림을 처리하기 위한 분산 스트리밍 플랫폼이다. 메시지를 토픽(topic)으로 분류하고, 발신자(producer)와 수신자(consumer)가 토픽을 통해 메시지를 주고 받는다. 카프카를 통해 비동기식 처리를 하는 방법은 메시지 큐에 발행하는 작업을 하는 A와 그 메시지를 가져가는 B를 producer, consumer로 둠으로써 가능하다.
Blocking
Blocking은 무언가를 차단하거나 방해하는 행위라는 의미가 있다.
Blocking 동작방식은, 호출된 Callee B가 Caller인 프로세스 A의 제어권을 가지고, 자신이 return 될 때까지 이 제어권을 넘겨주지 않는다.
Kernel 모드를 생각해보자.
- System call/Interrupt가 걸리면 CPU는 해당 작업을 처리하기 위해 현재 실행 중인 프로세스를 중단하고, Kernel 모드로 전환한다.
- 커널이 이 인터럽트에 대한 처리 마치면 CPU는 다음에 처리할 프로세스를 선택한다.
이 상황에서 커널모드로 들어가는 순간,
프로세스는 제어권을 빼았겨 아무것도 못하게된다. (Blocking)
Blocking의 전파 특성
Blocking은 전파되는 특징이 있다.
Non-Blocking인 함수와 Blocking인 함수를 실행하면 Caller 입장에서는 동작 전체가 블로킹된다. (자세한 내용 - 내가 만든 WebFlux가 느렸던 이유(NHN))
이러한 이유로 인해서 Non-Blocking을 적용하고싶다면,
영향이 가는 모든 프로세스들이 Non-Blocking해야 한다.
Non-Blocking
Blocking과 반대로 다른 작업이 호출되더라도, 제어권을 위임하지 않고 자신의 일을 처리하는 방식이다.
- 파일 시스템 읽기 및 쓰기, 네트워크 요청 등은 이를 처리하기 위해 Thread가 많은 대기를 한다. 이럴 때 Non-Blocking을 사용하면,작업의 효율을 높일 수 있다.
Synchronous & Asynchronous, Blocking & Non-Blocking 의 조합
수많은 예제가 있지만 내가 이해한 바로는 다음과 같다.
Blocking | Non-Blocking | |
Sync | Caller는 Callee가 종료될 때까지 기다려야하며, Callee가 일을 처리할 때까지 아무것도 하지 못한다. 일반적으로 우리가 프로그래밍 할 때 함수를 호출하는 방식 |
Caller는 Callee에게 제어권을 주지않지만 Callee의 종료 여부는 여전히 중요하다. 그래서 Callee의 종료여부를 계속 확인한다. Caller는 Callee의 작업 여부를 확인하면서, Non-Blocking하게 자기 자신의 일을 할 수 있다. |
Async | Caller는 Callee의 작업 종료 여부와 상관없이 일을 수행하지만, 제어권을 넘겨줬기에 사실상 기다려야한다. |
Caller가 Callee를 호출하고 자신의 일을 계속 진행한다. 호출된 Callee는 따로 작업을 수행하고 return을 위해 콜백함수를 실행한다. |
중요한 개념을 정리해보았다.
스프링 부트에서 썼던 @Async의 동작방식을 더 명확하게 이해하고,
내가 짠 코드에는 문제가 없을지/정말 Async한 구현방식이 맞는지 돌아볼 수 있어서 좋았다.
해당 내용은 여기 게시물에 추가하였다. 활용편으로 보면 좋을 듯하다.
'CS > OS' 카테고리의 다른 글
[OS] 멀티 프로세서, 멀티 태스킹, 멀티 프로세스, 멀티 스레드! (0) | 2024.03.10 |
---|---|
[OS] user mode & kernel mode (0) | 2024.01.30 |
[CS] 비트마스킹 | 비트 연산자 | 비트마스킹과 집합 (0) | 2023.07.19 |
[OS] CPU Scheduling | preemptive & non-preemptive | 스케줄링 알고리즘 | 스케줄러 알고리즘 평가 (0) | 2023.05.11 |
[OS] 운영체제가 하는 일4가지 (0) | 2023.04.15 |