본문 바로가기
CS/밑바닥

동기와 비동기, 블로킹과 논블로킹

by 핏차 2024. 6. 26.

 

동기 vs 비동기

동기: 종속적, 연관된, 기다림 = 전화 통화
비동기: 비종속적, 무관한, 기다릴 필요 없는, 동시 발생 = 이메일

 


동기(synchronization)

동기 방식이란?

동기 방식은 일반적인 함수 호출 방식으로,

funcA 함수가 funcB 함수를 호출하면 funcB 함수 실행이 완료될 때까지 funcA 함수의 다음 코드는 실행되지 않고 대기한다.

이 때 보통 funcA 함수와 funcB 함수가 동일한 스레드에서 실행된다.

 

동기 호출은 호출자/수신자가 동일 스레드에서 실행되는지 여부는 상관 없다.

 

예를 들어 동기 호출에서 비교적 특수한 상황으로 입출력 작업(blocking input/output)을 보면,

read 함수를 호출하여 파일을 읽는 작업을 할 경우

  1. 최하단 계층은 실제로 시스템 호출(system call)로 운영 체제에 요청을 보내고,
  2. 운영 체제는 호출 스레드를 일시 중지시키고 파일 읽기 작업을 한다.
  3. 커널이 디스크 내용을 읽어 오면
  4. 일시 중지되었던 스레드가 다시 깨어난다.

이렇게 호출자와 수신자가 다른 스레드에서 실행되기도 한다.

 

동기 방식의 장단점

동기 프로그래밍은 프로그래머가 이해하기 가장 쉽지만,

호출자가 요청한 작업이 끝날 때까지 대기해야 하기 때문에 효율은 높지 않다.

 

 

비동기(asynchronization)

비동기 방식이란?

비동기 호출은 디스크의 파일 입출력, 네트워크 데이터 송수신, 데이터베이스 작업처럼 시간이 많이 걸리는 작업을 백그라운드 형태로 실행하는 데에 주로 사용된다.

 

위 동기 호출 상황과 같이 입출력 작업에서 read 함수를 비동기 방식으로 호출한다면, 호출자가 블로킹되지 않고 read 함수가 즉시 반환되기 때문에 호출자는 즉시 다음 작업을 실행할 수 있다.

 

비동기 방식의 장단점

비동기 프로그래밍은 프로그래머가 이해하는 데 부담이 있을 수 있지만,

호출자의 이후 작업이 비동기 호출 작업과 동시에 진행되므로 높은 효율성을 가진다.

 

비동기 후처리

이 때, 비동기 호출 방식에서 작업이 실제로 완료되는 시점은 어떻게 파악할 수 있을까?

실행 결과 처리에 대한 관점은 두 가지 상황으로 나뉠 수 있으며, 이에 대한 방안은 다음과 같다.

  1. 호출자가 실행 결과를 전혀 신경 쓰지 않을 때
    1. 콜백 함수를 전달하여 호출자와 다른 스레드(프로세스)에서 처리되도록 한다.
  2. 호출자가 실행 결과를 반드시 알아야 할 때
    1. 알림(notification) 작동 방식을 사용하여 작업 실행이 완료되면 호출자에게 완료 신호나 메시지를 보낸다.
    2. 이 경우, 결과 처리는 호출 스레드(프로세스)에서 한다.

 

웹 서비스 로직이 하나 있다고 가정하고, 동기와 비동기 작업을 그림으로 살펴보자.

일반적으로 많이 호출되는 웹 서비스 데이터베이스 요청 처리를 동기, 비동기로 각각 처리하면 다음과 같을 것이다.

 

- 동기 방식

주 스레드가 다른 스레드의 작업 시간 동안 대기하며 생긴 빈 시간을 유휴 시간이라고 한다.

이 유휴 시간 동안 데이터베이스 처리가 완료될 때까지 기다려야 다음 과정을 처리할 수 있어 비효율적이다.

 

 

- 비동기 방식

비동기 방식은 두 가지 상황이 존재한다.

  1. 주 스레드가 데이터베이스 처리 결과를 전혀 신경 쓰지 않는 경우
  2. 주 스레드가 데이터베이스 작업 결과에 관심을 가지는 경우

 

1번 상황에서는 주 스레드가 아닌 데이터베이스 스레드가 다음 D, E, F 세 단계를 자체적으로 처리한다.

이 때, 데이터베이스 스레드는 D, E, F 세 단계에 대한 작업 방법을 어떻게 알 수 있을까? 콜백 함수로 알 수 있다.

소프트웨어 조직 구조 관점에서 볼 때, 이 작업은 데이터베이스 스레드에서 해야 할 작업이 아니기 때문에 DB 처리 후 콜백 함수를 호출하기만 하는 것

// 콜백 함수
void handle_DEF_after_DB_query() {
	D;
  	E;
  	F;
}

// 주 스레드가 데이터 처리 요청을 보낼 때 이 함수를 매개변수로 전달
DB_query(request, andle_DEF_after_DB_query);

 

위 그림처럼 주 스레드의 '유휴 시간'이 없어지고 그 자리를 작업이 채우고 있어 효율적이고 요청 처리 속도가 훨씬 빨라진다.

 

 

2번 상황에서는 데이터베이스 스레드가 알림 작동 방식을 이용하여 작업 결과를 주 스레드에게 알려줘야 한다. 주 스레드는 다음 요청을 처리하다가 결과를 수신하면 이전 해당 요청의 뒷부분을 계속 처리한다.

데이터베이스 스레드가 길게 유휴 상태지만, 주 스레드에 '유휴 시간'이 없어 1번 상황만큼은 아니지만 동기 호출에 비하면 효율적임을 알 수 있다.

 

물론 모든 비동기 호출이 동기 호출보다 더 효율적인 것은 아니다. 구체적인 상황에 따라 다를 수 있다.

블로킹과 논블로킹

블로킹(blocking): 함수 A가 함수 B를 호출할 때, 운영 체제가 A가 실행 중인 스레드나 프로세스를 일시 중지시키는 호출 방식
  (≒ 피자 가게에 직접 가서 포장 주문)
논블로킹(non-blocking): 다른 함수를 호출할 때, 실행 중인 스레드나 프로세스가 중단되지 않는 호출 방식
  (≒ 피자 가게에 전화로 배달 주문)

 

 

블로킹 방식

함수 호출로 인해 호출자의 스레드나 프로세스가 블로킹되는 경우는 일반적으로 입출력 상황이다.

디스크 입출력의 하나의 작업 수행 시간 단위가 ms인데, CPU는 GHz 단위 수준의 clock rate를 가지고 있기 때문에 이 ms 단위의 시간에 많은 기계 명령어가 수행될 수 있다. 따라서 스레드 A에서 입출력 작업 처리가 필요할 때는 그 시간 동안 스레드 A를 잠시 중단한다. 이 때 스레드 A는 입출력 작업을 실행하여 블로킹되었다고 말할 수 있다.

 

이 때, 스레드 A가 블로킹되는 동안 CPU 제어권을 다른 스레드에 넘겨 다른 작업을 할 수 있도록 해야 한다. 운영 체제는 CPU의 리소스를 최대한 효율적으로 할당해야 하기 때문이며, 이것이 바로 블로킹 입출력 방식의 핵심이다.

 

 

논블로킹 방식

시간이 많이 걸리는 입출력 작업이 포함될 경우, 호출 스레드(프로세스)가 블로킹되는 상황이 생긴다.

호출 스레드가 일시 중지되지 않으면서 입출력 작업을 시작하려면 논블로킹 호출 방식을 사용할 수 있다.

 

논블로킹 함수 A를 호출하면, 운영 체제는 스레드를 일시 중지시키는 대신 A 함수를 즉시 반환한다. 이후 호출 스레드는 작업을 계속 진행하며, 함수 A의 작업은 커널이 처리한다. 이 두 작업이 병행 처리되는 것이다.

그렇다면, 함수 A의 작업이 완료되었는지 어떻게 알 수 있을까?

이것을 알 수 있는 세 가지 방법이 있다.

  1. 함수 A 외에 결과를 확인하는 함수 B를 함께 제공하고, 함수 B를 호출하여 결과를 확인한다.
  2. 알림 작동 방식을 사용한다.
  3. 콜백 함수로 작업 완료 처리 함수를 전달한다.

이런 유형의 논블로킹 호출을 비동기 입출력(asynchronous input/output)이라고도 한다.

하지만 이런 논블로킹 호출 상황에서도 비동기 방식이 아니게 될 수가 있는데, 1번 방법에서 함수 B를 지속적으로 호출하여 확인하는 경우에는 결국 다른 작업을 하지 못하게 되어 더 이상 비동기가 아닌 동기 방식이 되어 버린다.

 


동기 / 비동기 + 블로킹 / 논블로킹 조합

수학적 명제로,

[블로킹]이면 [동기]이다. ([블로킹] → [동기])

[비동기]이면 [논블로킹]이다. ([비동기] → [논블로킹])

 

[동기]이지만 [블로킹]이 아닐 수 있고,

[논블로킹]이지만 [비동기]가 아닐 수 있다.


 

p.s. 지금까지 다룬 이야기와 앞으로 다룰 이야기

 

운영 체제, 프로세스, 스레드, 코루틴, 콜백 함수, 동기, 비동기, 블로킹, 논블로킹에 대해서 다뤄보았다.

이 기술들로 고성능 서버를 구현할 수 있다.


프로그래밍 기술의 발전 과정

다중 프로세스(e.g. fork 방식)

다중 스레드(프로세스 주소 공간 공유)

이벤트 기반 프로그래밍(이벤트 순환)

(이벤트 핸들러가 같은 스레드, 함수 하나에 이벤트 하나)

+ 입출력 다중화(e.g. epoll) 기술을 더한 이벤트 순환

(함수 하나로 여러 이벤트)

+ 다중 스레드를 활용한 이벤트 순환

(이벤트 핸들러가 각각 다른 스레드)

 

이벤트 순환 과정에 입출력 처리가 있다면 블로킹 인터페이스가 호출되면 안 된다. 논블로킹 인터페이스는 호출 가능하다.

 


비동기와 콜백 함수

 

 


코루틴: 동기 방식의 비동기 프로그래밍

 

 


CPU, 스레드, 코루틴의 관계

728x90