지난 포스팅에서 select 모델의 특징을 알아보며 블로킹(Blocking)과 비동기(Asynchronous) 입출력 모델이라 설명을 하였다. 컴퓨터공학 분야에서는 기술적 구현에 따라 블로킹과 논블로킹, 그리고 호출된 함수와 호출된 함수의 순차를 신경쓰는가 아닌가에 따라 동기와 비동기로 나뉘어진다.
 이번 포스팅에서는 이 개념에 대해서 상세히 다뤄보겠다.


1. 블로킹(Blocking) vs 논블로킹(Non-Blocking)

 블로킹과 논블로킹은 기술적인 구현을 통해 나뉘는 개념이다. 코드를 통해 블로킹으로 구현하였는가, 논블로킹을 구현하였는가에 따라 프로세스의 CPU 유휴상태가 달라진다.

1) 블로킹(Blocking)

Blocking방식

- 블로킹은 함수A가 함수B를 호출하였을때, 제어권을 함수 B에게 넘겨준다. A는 B의 결과가 반환되기 전까지 아무런 작업도 하지않고 대기한다. 
- 마찬가지로 입출력 요청이 발생했을때, 응용프로그램(Application)은 커널(Kernel)에게 시스템 요청(System call)을 하며 커널의 입출력 처리가 마칠때까지 입출력을 요청한 응용프로그램의 스레드는 아무런 작업을 진행하지 않고 기다린다.

※ 시스템 호출(system call): 응용프로그램에서는 수행할 수 있는 기능이 제약적이다. 따라서 특정 기능의 수행이 필요할 때 커널의 도움이 필요하다. 이때 커널의 기능을 사용하기 위해 요청하는것이 시스템 호출이다. 

-  단일 스레드로 블로킹 방식의 입출력을 한다면, 입출력 작업이 완료될 때 까지 응용프로그램이 멈춰있는다. (이전에 구현했던 send()/recv()함수를 통한 서버와 클라이언트가 그 예시이다.)
- 멀티 스레드로 다수의 클라이언트를 서비스하는 환경에서 문맥 교환(Context Switching)이 잦다. CPU는 실행중인 스레드가 입출력 작업으로 인해 일시적으로 중지되면 다른 스레드에게 CPU자원을 할당하여 실행시킨다. 이때 CPU의 자원을 할당받아 실행시키는 작업이 문맥 교환이라고 한다. 문맥 교환 작업이 일어나는 동안은 프로세스 처리하는 작업을 못하게 되므로 오버헤드가 발생하여 효율이 떨어진다.

※ 오버헤드(overhead): 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간·메모리 

이렇게 블로킹 I/O는 비효율성이 있기때문에 이를 개선하기 위한 논블로킹 모델이 고안되었다.


2) 논블로킹(Non-Blocking)

Non-Blocking 방식

- 논블로킹은 함수A가 함수B를 호출하였을때, B는 데이터가 준비되지 않아도 곧바로 반환을 한다. 이때 함수A가 가지고 있는 제어권도 B에게 넘겨준 즉시 A가 돌려받는 것이다.
- 함수 B가 반환한 데이터는 신뢰성이 없기때문에, A의 작업을 수행하면서 주기적으로 B가 원하는 결과값이 구해졌는지 체크가 필요하다. 이 작업을 폴링(polling)이라고 한다

- 논블로킹 입출력 모델은 응용 프로그램이 커널에게 시스템 요청을 했을때, 곧바로 응답을 반환한다. (예를들어 windows에서는 WSAEWOULDBLOCK라는 응답을 바로 반환한다)
- 멀티 스레드 환경에서 입출력 요청이 발생했을때 다른 스레드를 실행시키기 위한 문맥교환이 필요없다. 입출력을 요청한 스레드는 중단없이 실행되기 때문이다.
- 블로킹 형식보다 개선된 효율성을 보이지만 논블로킹이 만능은 아니다. 다수의 클라이언트로부터 동시다발적인 요청이 온다면, 커널에서 원하는 응답값이 준비되었는지 확인하는 과정에서 많은 비용이 들기 때문이다.
- 운영 체제들은 polling작업을 커널에서 지원하여 효율을 높인 I/O 모델들을 지원한다. 리눅스의 epoll이 그 예이다.


2. 동기(Synchronous) vs 비동기(Asynchronous)

 동기와 비동기는 기술로 구분되는 것이 추상적인 개념이다. 블로킹 논블로킹은 제어권이 누구에게 있느냐가 관심이였다면, 동기와 비동기는 호출한 함수가  작업들의 순서가 보장되느냐 아니냐가 관심사이다

1) 동기(Synchronous)

Synchronous 방식

-  동기방식은 함수A가 호출되었을때, 함수B는 먼저 호출된 함수A의 종료까지 기다린다. 함수A가 종료된 후에서야 함수B가 실행된다.
- 함수 작업A가 완료되는동안 함수B는 기다리고 있기때문에 얼핏 블로킹과 유사해보이지만, 동기모델의 관심사는 순차적인 작업의 순서를 보장한다는 것이다. 

- 동기 입출력 모델에서는 입출력이 발생하였을 때, 응용프로그램의 스레드는  커널에게 입출력의 입출력 작업이 진행되는 동안 아무런 작업을 하지않고 기다린다. 이후 커널의 작업이 끝나면 응용프로그램의 스레드는 다음 작업을 진행한다.
- 동기 입출력 모델에서 응용프로그램이 입출력을 마칠때까지 기다린다는 점에서 마치 블로킹과 유사하지만, 블로킹 형태의 입출력은 CPU자체의 처리를 입출력 작업이 끝나기 전까지 막는것을 뜻하며, 동기-논블로킹 형태로 구현도 가능하다. 입출력함수를 논블로킹으로 설정하면 CPU자체는 곧바로 반환하지만 동기형식이기 때문에 입출력 결과를 계속해서 확인하는 polling작업을 하면서 CPU는 busy-wait(CPU가 계속 프로세스를 작업하며 대기하는 상태)가 될 수 있다.

- 동기방식은 순서를 기다리기 때문에 효율성이 떨어질때도 있지만, 간단한 작업들이 순차적으로 필요할때는 효율적일 수 있다.


2) 비동기(Asynchoronous)

Asynchronous 방식

- 비동기방식은 함수A가 먼저 호출되었다고 해도, 뒤이어 호출되는 함수B와 함수C는 앞서 호출된 함수의 완료여부에 상관없이 실행된다.
- 각 함수들의 호출 순서와 함수들의 완료가 같은 순서가 된다고 보장할 수 없다.
- 함수B와 함수C의 시점에서는 선행된 작업을 기다리지 않아서 논블로킹과 유사해보이지만, 비동기는 순서를 지키지않고 작업을 진행한다는 것이 관심사이다.

- 비동기 입출력 모델에서는 응용프로그램이 입출력 요청이 발생했을때, 응용프로그램 스레드는 커널에게 작업을 맡긴 후 완료를 기다리지 않고 다음 작업들을 진행한다.
- 비동기와 논블로킹이 유사해보이지만, 입출력 요청이 비동기형태로 구현되어도 블로킹 형태로 처리될 수 있다.

- 여러 작업들을 병행해서 가능하므로 효율을 높일 수 있다. 하지만 구현에 있어서 보다 더 복잡한 문제를 고민해야할 가능성이 동기방식보다 높다. 


 이번 포스팅을 통해 블로킹(Blocking)과 논블로킹(Non Blocking) 그리고 동기(Synchronous)와 비동기(Asynchronous)에 대해 상세히 알아봤다.
 사실 동기방식은 블로킹, 비동기방식이 논블로킹이 구현에 있어서 편리함이 있기에 두 개념이 묶여질 때가 많다. 하지만 이들은 별개의 개념이다. 다음 포스팅에서는 이 개념들이 조합된 형태에 대해 다뤄볼 예정이다.

+ Recent posts