-
[OS]Blocking/Non-Blocking/Sync/Async의 차이를 설명할 수 있는가?카테고리 없음 2022. 8. 13. 10:52
0. 들어가기 전
Blocking vs Non-blocking & 동기 vs 비동기
이거 정말 차이가 있는 거 맞나? 그냥 같은거 아닐까. 사실 대부분의 조건에서 이들은 비슷한 상황을 설명한다.
기본적인 개념은
Blocking/Non-Blocking은 호출되는 함수가 호출한 함수에게 제어권을 넘겨주는 방식인지 넘겨주지 않는 방식인지에 대한 구분이다.
동기/비동기는 호출되는 함수의 작업 완료 시 호출한 함수가 결과를 즉시 처리하는 방식인지 나중에 처리하는 방식인지에 대한 구분이다. 두 작업의 시간이 어떻게 다루어지는 지가 중요하다.
다른 블로그에서 비슷하게 적은 것을 많이 봤을 것이다. 하지만 난 도저히 이해가 안된다. 그래서 최대한 블로그를 많이 참고하고 내가 이해가 가능한 글들만 모아서 간단하게 정리해보았다.
1. Blocking vs Non-Blocking
Blocking
Blocking I/O에서는 시스템 콜을 호출한 프로세스의 실행이 중지된다.
- 유저 모드의 프로세스가 시스템 콜을 실행한다.
- I/O가 끝날 때까지 유저 프로세스는 대기한다. I/O 작업의 결과가 반환되면 프로세스의 block이 풀린다.
Non-Blocking
Non-Blocking은 I/O와 밀접하게 관계되어 있다.
- Non-blocking I/O에서는 I/O 작업을 진행하는 동안 유저 프로세스의 작업이 중지되지 않는다.
- 유저 모드의 프로세스가 시스템 콜을 실행한다.
- I/O 작업 진행 상황과 관계 없이 결과를 반환한다. 만약 완료되지 않은 경우라면 오류 메시지를 반환.
- 즉, I/O 작업 진행이 완료된 것을 확인하기 위한 방법이 있어야 한다. polling이 그 방법 중 하나일 뿐이다. (시스템 콜을 통해 지속적으로 I/O 작업 진행 상황을 확인하거나 안하거나. 확실한 건 오류라도 반환하고, 나는 다른 일 한다.)
2. Sync vs Async
Synchronous
HTTP 요청은 비동기다. 요청을 보내고 응답을 기다린다.
- 동기 I/O에서는 즉각적인 결과를 기대하고 있다.
- 작업을 동시에 수행하거나, 동시에 끝내거나, 끝나는 동시에 시작함을 의미한다.
- 순서대로 실행되는 코드들. 다음 줄로 진행하기 전에 해당 줄이 끝나야 한다.
Asynchronous
비동기는 메일과 같다. 메일을 보낸 뒤, 응답을 즉시 기대하지 않는다.
- 비동기 I/O에서는 사용자 모드의 프로세스가 I/O의 작업 상황과 상관 없이 다른 프로세스 작업을 진행한다. (context-switching 으로 인한 오버헤드)
- 해당 줄이 끝나지 않아도 다음 줄의 코드를 실행하는 코드와 같다.
- I/O 작업이 완료되면 이벤트를 발생시켜 결과를 전달한다.
예를 들어 정리해보자, A가 B에게 “당당치킨을 구매할 수 있는 지”물어본다.
- Blocking : A는 그 자리에 서서 대답을 기다린다. A는 Blocking이다.
- Non-Blocking : A는 그 자리를 떠난다. A는 다른 일을 한다. B에게 작업이 끝났는지 주기적으로 물어보는지, B가 작업이 끝났음을 알려야 다시 되돌아 오는지 우리는 모른다. 단지 A는 B에게 물어본 뒤 다른 일을 하고 있다는 것은 확실하다.
- Synchronous : A는 그 자리에 서서 대답을 기다린다. A와 B는 동기다.
- Asynchrounous : A는 그자리를 떠난다. A는 다른 일을 한다. A는 B가 부르기 전까지 다시 돌아오지 않을 것이다. A와 B는 비동기이다.
3. 조합
node.js의 호출 패턴을 예로 들자면 블로킹 메서드는 동기로 실행되고 논블로킹 메서드는 비동기로 실행된다. 이게 가장 일반적인 경우이기 때문이다.
다음은 동기로 파일을 읽는 코드이다.
const fs = require('fs'); const data = fs.readFileSync('/file.md'); // 파일을 읽을 때까지 여기서 블로킹 됩니다.
전체 파일을 읽을 때까지, 다른 JavaScript 실행이 블로킹 된다. 동기로 파일을 읽을 경우, 오류가 발생하면 반드시 처리해야 하고 그렇지 않으면 프로세스는 죽을 것이다.
다음은 비동기로 파일을 읽는 코드이다.
const fs = require('fs'); fs.readFile('/file.md', (err, data) => { if (err) throw err; console.log(data); }); moreWork(); // console.log 이전에 실행될 것입니다.
아래의 내용은 IBM의 2:2 매트릭스 모델로 동기, 비동기, blocking, non-blocking의 조합에 대한 설명이다. 많은 블로그들이 해당 그림으로 설명하고 있지만, 내가 공부한 내용과는 달라서 정확한 설명인 지 확신이 들지 않는다.
Synchronous Blocking I/O
I/O 요청 후 결과 대기, 결과 반환 후 즉시 처리. 가장 일반적인 모델 중 하나이다.
시스템 콜이 완료될 때까지 애플리케이션이 차단된다. CPU를 사용하지 않고 응답을 기다리는 상태가 된다.
애플리케이션의 관점에서 read() 호출은 오랜 시간 소모된다. 하지만 실제로 읽기가 커널에서 작업되는 동안 애플리케이션은 block된 상태이다.
Synchronous Non-Blocking I/O
동기로 실행해야 하지만 I/O를 즉시 완료할 수 없으므로 오류 코드를 반환해야 한다. I/O의 진행 상황을 반복적으로 물어보고 결과를 반환한다.
애플리케이션은 효율을 위해 커널에서 명령이 수행되는 동안 다른 작업을 시도해야 하지만 이러한 방식에서는 불가능하다.
Asynchronous Blocking I/O
I/O 요청 후 결과 대기, 결과 반환 후 즉시 처리 하지 않음. 매우 비효율적인 방식이다.
이런 경우가 발생하는가? 일반적인 경우에 발생하지 않음, 일종의 실수라고 생각.
MySQL & Node.js : Node.js가 비동기 처리를 위해 오지만, MySQL의 드라이버는 blocking 방식이다.
Asynchronous Non-Blocking I/O
I/O 요청 후 CPU 작업 재개, 결과 반환 후 즉시 처리 하지 않아도 됨. 일반적인 경우이다. 제어권을 I/O에게 주지 않고 다른 프로세스를 처리한다.
JS : API 요청 후, 콜백을 통해 해결.
출처
https://docs.microsoft.com/ko-kr/windows/win32/fileio/synchronous-and-asynchronous-i-o
- 유저 모드의 프로세스가 시스템 콜을 실행한다.