block... non-block... 많이 들어 보고 추상적인 개념은 알겠는데.. 정확히 알지 못해서 정리!
input/output, 데이터의 입출력을 의미한다.
network(socket), file, pipe(process간 통신), device(모니터, 키보드 등) I/O가 존재!
네트워크 통신은 socket을 통해 데이터가 입출력 된다.
컴퓨터끼리 통신을 하려면 꼭 컴퓨터의 프로세스안에 소켓을 열고 소켓을 통해 데이터를 주거나 받을 수 있다.
backend server는 클라이언트와 각각 소켓을 열고 통신을 한다.
그래서.. block I/O 와 non-block I/O가 어떻게 동작하는가?! OS레벨에서 개념적으로 살펴보자!
I/O 작업을 요청한 프로세스/스레드는 요청이 완료될 때까지 블락됨.
1. thread에서 read blocking system call 호출을 한다.
2. read blocking system call을 호출한 thread는 block이 된다.
3. kernel mode로 전환 되고 kernel에서는 read I/O를 실행시킨다.
4. 시간이 지나 device에서 읽을 준비가 되었다고 read response를 준다.
5. kernel이 응답을 받고 데이터를 kernel space 에서 user space로 옮기게 된다.
6. thread는 block에서 깨어나 나머지 작업을 진행하게 된다.
socket마다 send_buffer, recv_buffer 두개의 버퍼가 있다.
socket S에서 socket A로 데이터를 보내려고 한다. 그때 recv_buffer는 데이터가 들어올 때 까지 해당 read blocking system call을 한 thread는 block이 된다.
socket S에 write를 하려고 하면 send_buffer에 데이터를 쓰게 된다. 이때 종종 send_buffer가 가득차게 되는데 그때 write system call은 send_buffer가 비어서 공간이 생길 때 까지 block이 된다.
process/thread를 block시키지 않고 요청에 대한 현재 상태를 즉시 리턴.
블락되지 않고 즉시 리턴하기 때문에 스레드가 다른 작업을 수행할 수 있다.
1. thread에서 read non-blocking system call을 호출한다.
2. kernel mode로 context-switching되고 kernel mode에서 read I/O를 실행한다.
3. kernel에서 아직 데이터가 준비되지 않았기 때문에 -1(Linux 기준)값을 리턴시킨다.
4. thread는 non-block으로 호출 했기 때문에 block이 되지 않고 이어서 다른 코드를 실행 할 수 있다.
5. 그 와중 I/O device로 부터 read response가 kernel로 오고 kernel은 데이터를 준비해 놓는다.
6. 한편 thread는 쭉 실행을 하다가 다시 read non-blocking system call을 호출한다.
7. 다시 kernel mode로 context-switching되고 이때는 데이터가 준비가 되어 있기 때문에 데이터를 kernel space에서 user space로 데이터를 전송한다.
socket S에서 socket A로 데이터를 보내려고 한다. socket A에 recv_buffer에 데이터가 있는지 read를 호출한다. 만약 block I/O라면 block이 되지만 지금은 non-block I/O기 때문에 없다고 알려주고 read에 대한 system call은 바로 종료가 된다.
마찬가지로 socket S에서 write를 할 때도 block I/O였다면 send_buffer가 가득 찼을 경우 send_buffer에 공간이 생길 때 까지 block이 되지만 non-block I/O는 block이 되지 않고 적절한 에러 코드와 함께 write에 대한 system call은 바로 종료가 된다.
I/O 작업 완료를 어떻게 확인할 것인가?
관심있는 I/O 작업들을 동시에 모니터링하고 그 중 완료된 I/O작업들을 한번에 알려준다.
한번의 system call로 여러 이벤트에 대해 한번에 처리하는 방식이다.
종류로는 select(사용 x), poll(사용 x), epoll(linux), kqueue(mac OS), IOCP(window) 등이 있다.
네트워크 통신에 많이 사용된다.
1. thread는 I/O multiplexing system call을 이용해서 kernel에 요청한다.
monitor 2 sockets non-blocking read -> 2개의 socket에 대해서 non-blocking mode로 읽으려고 하니까 새로운 들어오는 데이터가 있는지 알려줘! 라는 뜻.
2. kernel은 2개의 socket에 대해서 read I/O요청을 보낸다.
3. multiplexing system call을 한 thread는 block이 될 수 있고 non-block이 될 수 있다. (지금은 blocking mode로 동작한다고 가정하자)
4. block이 되는 동안 비슷한 타이밍에 2개의 socket에 대한 데이터가 모두 들어왔다.
5. kernel에서 데이터가 있다는 것을 알려준다.
6. thread는 blocking mode에서 깨어나고 순차적으로 2개의 socket에 관한 데이터를 읽어온다.
Spring Boot Webflux를 공부할 때 non-blocking에서 부터 막혀서 이해하기 어려웠는데 이제는 이해 할 수 있을 것 같다!
아 근데 또 한가지 궁금한점이 생겼다.. NodeJs도 non-blocking인데 그러면 Webflux랑 node의 차이가 뭘까..? 둘다 single thread에 non-blocking으로 알고 있는데.. 다음에 이걸 공부 해봐야 겠다!