개발 일기

[OS] Block I/O vs Non-Block I/O 본문

Computer Science/Operating System

[OS] Block I/O vs Non-Block I/O

개발 일기장 주인 2024. 12. 7. 16:25

💡 I/O란 무엇인가요?

  • I/O는 Input/Output의 줄임말로, 컴퓨터 시스템에서 데이터의 입출력을 의미
  • 우리가 키보드를 통해 입력을 하거나, 파일을 저장하는 행위 모두 I/O의 일종
  • 즉, I/O는 프로그램과 외부 세계(또는 다른 프로그램) 간의 소통 수단입니다.

🧾 I/O의 종류

I/O는 다양한 방식으로 분류 가능하다.

  1. File I/O
    파일을 읽거나 쓰는 작업입니다. 예: 로그 기록, 설정 파일 불러오기 등
  2. Device I/O
    키보드, 마우스, 프린터 등과의 통신입니다.
  3. Pipe I/O
    프로세스 간의 통신(IPC)을 위한 방법입니다. 예: 리눅스에서 | 파이프 명령 사용
  4. Network(Socket) I/O
    네트워크를 통해 다른 컴퓨터 또는 서버와 데이터를 주고받는 방식입니다. 오늘은 이 Socket I/O에 대해 좀 더 깊이 들어가 보려고 합니다.

🔌 Socket I/O란?

소켓(Socket)은 네트워크 상에서 통신을 하기 위한 추상적인 연결 지점

즉, 네트워크를 통해 데이터를 주고받기 위해, 프로그램은 소켓을 열고 해당 소켓을 통해 데이터를 송수신한다.

쉽게 말해, 소켓은 "데이터를 주고받는 문"이라고 할 수 있다.

통신을 원하는 각 프로세스는 자신의 소켓을 열고, 이 소켓 간의 연결을 통해 네트워크 통신을 수행한다.


🚫 Block I/O란?

I/O 작업을 요청한 프로세스/스레드는 요청이 완료될 때까지 블락됨

 

OS관점에서 Blocking

  • read라는 system call을 실행 시 해당 system call이 blocking 이라면? 메인 스레드는 block이 된다.
  • 커널 모드에서 I/O가 실행이 되고 응답을 받고 읽은 data를 kernel에서 user로 이동시킨다.
  • 이때 블락 됐던 스레드는 전달된 데이터를 받고 다시 실행되기 시작한다.

Socket에서 Blocking

  • Socket S에서 A로 데이터를 송신하려고한다.
  • Socket A에서 read()를 하게되면 recv_buffer에 데이터가 들어오기 전까지 block된다.

  • Socket S 입장에서는 데이터를 전송하기 위해 send_buffer에 write()하게 된다.
  • 이때, send_buffer가 가득찼다면? 공간이 생겨서 write()가 처리될때까지 Socket S는 block된다.

🟢 Non-Block I/O란?

프로세스/스레드를 블락시키지 않고 요청에 대한 현재 상태를 즉시 리턴 스레드가 다른 작업 이어서 처리 가능

 

OS에서 Non-Blocking

  • 똑같이 메인 스레드에서 read system call을 호출하는데 이때 non-blocking이라면?
  • I/O 작업이 끝나지 않았지만 Linux기준으로 -1을 반환하고 그와 함께 EAGAIN 이나 EWOULDBLOCK이라는 에러코드도 함께 반환한다.
  • 우선 응답이 왔기 때문에 원레 스레드는 block되지않고 이어서 실행 가능하다.

  • 그러다가 다시 스레드는 read non-blocking system call을 실행하게 되고 thread가 실행될때 I/O작업이 완료됐기때문에
  • read한 데이터를 thread로 돌고와서 작업을 이어간다.

Socket에서 Non-Blocking

  • Non-blocking 모드의 소켓에서는 read()를 호출했을 때, 내부 수신 버퍼에 데이터가 없으면 -1과 함께 EAGAIN 또는 EWOULDBLOCK 에러를 반환하며, 호출한 스레드는 block되지 않고 바로 다음 작업을 수행할 수 있다.
  • Socket S의 write() System call도 마찬가지이다.

❓Non-Blocking I/O 이슈 : 작업 완료 확인 어떻게?

1. 완료됐는지 반복적 확인(Polling)

  • 가장 직관적인 방법
  • I/O 작업이 준비됐는지 계속해서 read()나 write()를 호출해보는 방식
  • 주기적으로 확인하기 때문에 time gap 발생 가능
  •  CPU 리소스를 매우 비효율적으로 사용하게 되어 낭비가 발생한다.
  • 수많은 소켓을 동시에 다루는 상황에서는 적합하지 않다. 각각의 소켓마다 리소스(CPU)를 들여서 확인해야 한다.

2. I/O Multiplexing(다중 입출력) 사용

  • 관심있는 I/O 작업들을 동시에 모니터링하고 그 중에 완료된 I/O 작업을 한번에 알려준다.
  • select, poll, epoll(Linux), kqueue(BSD) 같은 I/O 다중화 기술을 사용

 

3. callback 혹은 signal

  • signal을 통해 특정 시그널 핸들러가 실행되거나
  • callback 함수가 등록되어 자동 호출되기도 함

Blocking/Non-blocking에서 중요한 포인트는 "스레드의 제어권"과 "대기 여부"에 있다.

  • Blocking은 I/O 작업을 수행하는 동안 해당 스레드(예: 메인 스레드)가 작업이 끝날 때까지 제어권을 넘기지 않고 기다리는 방식이다. 즉, 다른 일을 하지 못하고 대기 상태에 빠진다.
  • Non-blocking은 I/O 작업이 완료되기를 기다리지 않고, 즉시 제어권을 반환해 다른 작업을 병렬로 처리할 수 있게 한다. 보통 별도의 스레드나 I/O 멀티플렉싱 기법을 통해 이후 I/O 완료 여부를 감지하고 처리한다.

Sync/Async에서 중요한 포인트는 "결과를 기다리느냐, 안 기다리느냐" 즉, "순서와 결과에 대한 관심도"에 초점이 있다.

  • Sync는 호출한 쪽이 결과가 나올 때까지 기다리고 다음 로직은 반드시 결과를 받아야 진행해야한다.
  • Async는 호출한 쪽이 결과 기다리지 않고 바로 다음 작업으로 넘어가는데 결과는 콜백 / Future / Promise 등을 통해 나중에 받아 처리한다. 즉, 순서가 별로 중요하지 않다.

Blocking/Non-blocking & Sync/Async 조합

🔹 Blocking / Sync

  • 호출한 스레드는 I/O 작업이 완료될 때까지 블로킹되고,
  • 제어권을 넘겨준다 (즉, 아무 작업도 못함).
  • Sync이기 때문에 결과와 순서에 관심이 많아서,
    작업이 완료되고 결과를 받은 뒤에야 다음 로직을 처리한다.

🔹 Non-Blocking / Sync

  • 호출한 스레드는 제어권을 계속 가지고 있고,
    I/O 작업이 완료되지 않아도 즉시 리턴된다.
  • 그래서 호출한 스레드는 이어서 자신의 작업을 이어가기는 한다.
  • 다만 Sync이기 때문에 결과가 중요해서,
    주기적으로 I/O가 완료됐는지 확인하고
    결과가 준비되었을 때 바로 후속 작업을 처리한다.
    (예: Polling, I/O Multiplexing 등)

🔹 Blocking / Async

  • 호출한 스레드는 블로킹되어 제어권을 넘겨주고 기다리지만,
  • Async이기 때문에 결과에 크게 관심이 없다.
  • 결과는 나중에 콜백 등을 통해 전달되며,
    기다릴 필요 없는 작업임에도 블로킹하고 있다는 점에서 비효율적일 수 있다.

🔹 Non-Blocking / Async

  • 호출한 스레드는 제어권을 계속 가지고 있으면서도,
    작업 결과에도 관심이 없어 기다리지 않는다.
  • 즉, 이어서 자신의 작업을 이어간다.
  • 결과는 나중에 콜백, Future, Promise 등을 통해
    별도의 로직에서 비동기적으로 처리된다.
  • 이 방식은 성능과 확장성 측면에서 가장 이상적이다

 

'Computer Science > Operating System' 카테고리의 다른 글

[OS] 프로세스(Process)와 스레드(Thread)  (0) 2025.03.21