Blocking / Non-Blocking, Synchronous / Asynchronous

2024. 1. 20. 15:13CS/운영체제

동기와 비동기 ? Blocking Non-Blocking?

개발을 공부하다 보면 동기 / 비동기라는 단어를 자주 접하게 됩니다. 또한 이 둘과 혼동될 수 있는 Blocking , Non-Blocking 개념도 종종 들을 수 있습니다. 미묘하게 다른 두 개념을 함께 살펴보겠습니다.

 

Blocking

OS관점의 블로킹은 프로세스가 어떤 작업을 위해 OS에 기능을 요청했는데 ( System Call ) OS가 해당 태스크를 바로 처리할 수 없을 경우, thread 를 “sleep” 혹은 “blocked” 상태로 변경합니다.

 

즉, CPU 큐에서 내려오고, 작업이 완료될 때 까지 대기합니다. 요청된 작업이 완료된 후에 OS는 thread를 깨웁니다.

이렇게 스레드를 sleep 상태로 만들 수 있는 함수를 “blocking” 함수라고 하며, 그러한 상태로 만들 수 있는 I/O 작업을 “blocking I/O” 로 지칭합니다.

Blocking I/O 는 스레드를 Sleep 상태로 변경합니다. 즉 , CPU 큐에서 내려오게 되므로, CPU 자원을 소모하지 않습니다.

하지만 그 순간에, 스레드는 아무 작업도 할 수 없게 되어 throughput 은 감소합니다.

  1. Application이 Read 작업을 수행 시 , kernel 에 System call 을 요청합니다.
  2. read I/O 가 완료될 때 까지 대기합니다. (CPU 큐에서 내려옵니다. )
  3. response 가 완료되면 kernel 은 결과값을 읽습니다.
  4. 완료된 데이터가 kernel space 에서 user space 로 이동합니다 .

System call 을 실행하고 결과 값을 반환 받기까지 사이 시간은 blocking 됩니다.

 

흔히 볼 수 있는 예시로는 Java 의 BufferedReader 가 있겠습니다. 

br.readLine() 메서드를 호출 시 개행문자("\n")가 나타날 때 까지 Blocking 됩니다. 


Non-Blocking

반면에 , 스레드를 sleep 시키지 않는 함수를 “Non-Blocking” 함수라고 부르고,

이러한 I/O 작업을 “Non-blocking I/O” 라고 부릅니다.

단순히 시간이 오래 걸리는지 아닌지가 아닌, 스레드를 sleep 시키는지가 중요한 판단 기준입니다.

따라서 어떠한 함수를 실행해서 결과값을 받기까지 시간이 매우 오래 걸리더라도, 스레드가 sleep 되지 않는다면, blocking 함수가 아닌 Non-blocking 함수입니다.

Non-blocking 함수들은 작업이 CPU 큐에서 내려오지 않고, 작업이 완료되었는지 지속적인 확인이 필요하기 때문에 (Polling) Busy Wait 이 발생할 수 있고, 이는 CPU 사용률을 올리게 됩니다. 하지만 작업 완료 시점은 동일하기 때문에 throughput은 올라가지 않습니다.

비동기 방식의 Read() 를 보겠습니다.

  1. Read() 시스템 콜을 호출합니다. Kernel 로 Context switch 가 일어나지만, 작업의 주도권은 아직 Application에게 있습니다.
  2. Kernel은 Read I/O를 진행합니다. Application은 작업이 완료되었는지 확인을 위해 Polling 을 시도합니다.
  3. 작업은 완료되지 않았기 때문에, EGAIN / EWOULDBLOCK 을 반환합니다 (Exception)
  4. kernel 에서 Read 가 완료되면, 결과를 반환할 수 있습니다. 다음 시점에 Application 이 결과를 요청시, Data를 커널에서 UserSpace 로 옮깁니다.

Blocking 과 Non-blocking 을 가로지르는 가장 큰 차이는 CPU 큐에서 내려오는지 ( sleep 상태 ) 입니다.

그에 따라서 작업의 주도권 또한 자연스럽게 호출한 Application 이 갖느냐, Blocking 함수가 갖는지 또한 결정되기 때문입니다.

 

자바의 nio는 Non-blocking 방식으로 동작합니다.

기존 Buffer 를 이용한 I/O 에서 Non-blocking으로 변경되어 중간 매개자인 Channel 을 사용하는데, 관련한 내용은 이후 포스팅에서 다루도록 하겠습니다. 


 

동기화는 일상 생활에서도 많이 쓰이는 용어입니다. 얼핏 들으면 동기와 비동기, 블로킹과 논블로킹 모두 같은 뜻 인가? 싶지만, 이 둘은 다른 범위와 관점을 가지고 있습니다.

동기/비동기는 작업의 처리 순서 관점으로, 완료 여부 기준으로 순차적으로 처리 할 것인지에 대한 내용이고,

Blocking / Non-Blocking 은 현재 작업에서 대기하느냐, 다른 작업을 진행하며 꾸준히 결과값을 polling 하느냐에 대한 차이를 가지고 있습니다.

Synchronous VS Asynchronous

 

위와 같이 동기식은 작업의 끝과 시작을 일치시켜, 실행한 작업을 순서대로 처리합니다.

하지만 비동기 방식은 작업의 순서가 지켜지지 않습니다.

먼저 완료된 작업은 먼저 반환됩니다.

따라서 멀티 프로세서 / 멀티 스레드(Worker) 를 사용하는 현대 아키텍처 기준으로는 비동기 방식의 개발이 성능 면에서 유리하다고 할 수 있습니다. 하지만 동기화 문제, 코드의 난이도 증가와 같은 trade off 가 있으므로, 취사 선택이 필요한 부분입니다.


혼동되기 쉬운 Blocking / Non-Blocking , Synchronous / Asynchronous 에 대하여 제 나름대로의 정리를 적어보았습니다.

그래도 다이어그램을 보면 “같은 소리 아닌가..?” 싶을 수 있습니다.

 

하지만 둘은 다른 범주이며, 동기/비동기 방식은 작업의 진행에 초점이 맞춰져 있고, Blocking/Non-Blocking 방식은 작업에서 진행되는 프로세스가 Sleep 되는지 / 아닌지 여부를 기억하시면 좋을 것 같습니다.

가장 유명한 IBM 다이어그램을 쉽게 설명한 차트입니다.

Sync / Async는 작업의 실행 플로우 (큰 화살표) , Blocking / Non-Blocking 은 Context Switch 시 그려지는 화살표를 참고하시면 이해가 빨라질 것 같습니다.

Linux 등에서 사용되는 작업 방식을 구분한 다이어그램입니다.

I/O Multiplexing 과 select() 에 관하여는 다음 포스팅에서 조금 더 자세하게 다루도록 하겠습니다.

감사합니다!

'CS > 운영체제' 카테고리의 다른 글

운영체제 - (2)  (0) 2024.11.07
운영체제 - (1)  (1) 2024.10.31