0Pricing
C Academy · 강의

블로킹과 논블로킹 I/O

이벤트 루프가 중요한 이유를 알아보세요.

블로킹과 논블로킹 I/O은(는) CoddyKit의 무료 C Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

블로킹의 의미

블로킹 시스템 호출은 작업을 진행할 수 있을 때까지 호출한 스레드를 중단합니다. 데이터가 없는 소켓에서 recv()를 호출하면 커널은 바이트가 도착할 때까지 스레드를 대기시킵니다.

이 방식은 이해하기 쉽습니다. 연결 하나에 스레드 하나를 사용하고 코드가 순차적으로 실행됩니다. 하지만 수천 개의 클라이언트를 동시에 처리해야 할 때 비용이 나타납니다.

ssize_t n = recv(fd, buf, sizeof buf, 0);
/* thread sleeps here until data or error */
if (n > 0) handle(buf, n);

확장성 문제

블로킹 입출력에서는 멈춘 클라이언트 하나가 전체 스레드를 막습니다. 고전적인 해결책은 연결마다 스레드나 프로세스를 하나씩 두는 것입니다.

어느 정도까지는 작동하지만 스레드 10,000개는 스택 10,000개와 무거운 문맥 전환, 스케줄러 오버헤드를 의미합니다. 이것이 서버를 이벤트 기반 설계로 이끈 유명한 C10k 문제입니다.

논블로킹 모드

논블로킹 소켓은 절대 대기하지 않습니다. 호출을 즉시 완료할 수 없으면 바로 -1을 반환하고 errno를 EAGAIN 또는 EWOULDBLOCK으로 설정합니다.

이제 나중에 다시 시도하는 것은 코드의 책임입니다. 따라서 단일 스레드가 여러 소켓을 번갈아 처리하면서 어느 하나에서도 멈추지 않을 수 있습니다.

ssize_t n = recv(fd, buf, sizeof buf, 0);
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
    /* no data right now, try again later */
}

fcntl로 O_NONBLOCK 설정하기

fcntl()로 O_NONBLOCK 플래그를 추가하면 디스크립터를 논블로킹으로 전환할 수 있습니다. 항상 먼저 현재 플래그를 읽은 다음 비트에 OR 연산을 적용해야 다른 설정을 덮어쓰지 않습니다.

이 동일한 도우미는 수신 대기 소켓, 수락한 클라이언트 소켓, 파이프 모두에 사용됩니다.

int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

부분 읽기 처리하기

논블로킹 입출력에서는 부분 작업이 일반적입니다. recv()는 요청한 바이트보다 적게 반환할 수 있고, send()는 버퍼의 일부만 수락할 수 있습니다.

얼마나 보내거나 받았는지 추적하고 그 지점부터 재개해야 합니다. 한 번의 호출이 모든 바이트를 이동시킨다고 절대 가정하지 마세요.

size_t sent = 0;
while (sent < len) {
    ssize_t w = send(fd, buf + sent, len - sent, 0);
    if (w < 0) { if (errno == EAGAIN) break; else return -1; }
    sent += w;
}

바쁜 대기는 잘못된 방식입니다

논블로킹 소켓을 사용하는 단순한 방법은 모든 소켓을 계속 순회하며 끊임없이 재시도하는 것입니다. 이런 바쁜 대기는 아무 일도 일어나지 않을 때에도 CPU를 100% 사용합니다.

실제로 원하는 것은 커널에 "준비된 디스크립터를 알려 주고, 그때까지 대기하게 해 달라"고 요청하는 것입니다. 이것이 바로 준비 상태 알림이 제공하는 기능입니다.

준비 상태 알림

입출력 다중화를 사용하면 한 스레드가 여러 디스크립터를 동시에 기다리다가 하나 이상이 준비되었을 때만 깨어날 수 있습니다. 커널이 대신 감시합니다.

고전적인 인터페이스는 select()와 poll()입니다. 작동하기는 하지만 호출할 때마다 모든 디스크립터를 다시 검색하므로 규모가 커지면 비용이 높아집니다.

fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
select(fd + 1, &rfds, NULL, NULL, NULL);

select와 poll이 확장되지 않는 이유

select()와 poll()은 모두 O(n)입니다. 호출할 때마다 전체 디스크립터 집합을 커널에 전달하고, 커널이 전부 검색한 다음, 준비된 항목을 찾기 위해 다시 전부 검색합니다.

select()에는 FD_SETSIZE라는 상한도 있으며, 대개 1024입니다. 수천 개의 연결에서는 이 오버헤드가 지배적이 됩니다.

epoll의 등장

epoll은 Linux의 확장 가능한 해법입니다. 디스크립터에 대한 관심을 한 번 등록하면 커널이 준비 상태를 추적하는 내부 자료 구조를 유지합니다.

각 대기는 실제로 준비된 디스크립터만 반환하므로 비용이 전체 연결 수가 아니라 활성 연결 수에 따라 증가합니다. 따라서 준비된 이벤트마다 대략 O(1)의 비용이 듭니다.

int epfd = epoll_create1(0);
/* register fds once, then wait for ready events */

논블로킹과 epoll 함께 사용하기

epoll과 논블로킹 소켓은 한 팀처럼 작동합니다. epoll은 디스크립터가 준비되었음을 알려 주고, 논블로킹 호출은 절대 대기하지 않고 데이터를 모두 처리할 수 있게 합니다.

epoll에 전달하는 소켓에는 항상 O_NONBLOCK을 설정해야 합니다. 그렇지 않으면 잘못된 깨우기나 부분 읽기가 단일 이벤트 루프 스레드를 막을 수 있습니다.

set_nonblocking(conn_fd);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);

개념적 모델

서버를 하나의 반복문으로 생각해 보세요. epoll_wait()에서 대기하고, 준비된 디스크립터의 짧은 목록을 받아 각각에 대해 논블로킹 작업을 수행한 뒤 반복합니다.

유휴 상태에서는 스레드가 대기하고 실제 작업이 있을 때만 깨어납니다. 이제 하나의 스레드로 수만 개의 연결을 효율적으로 처리할 수 있습니다.

빠른 확인

논블로킹 소켓에 대한 이해도를 확인해 보세요.

복습

블로킹 입출력은 단순하지만 연결마다 스레드 하나를 붙잡으므로 규모가 커지면 한계에 도달합니다. 논블로킹 입출력은 대기하는 대신 EAGAIN과 함께 즉시 반환합니다.

소켓을 촘촘한 반복문으로 폴링하면 CPU를 낭비하므로 준비 상태 알림을 사용합니다. select/poll은 O(n)이고, epoll은 수천 개의 연결로 확장됩니다. 다음으로 epoll을 설정하겠습니다.

자주 묻는 질문

“블로킹과 논블로킹 I/O” 강의는 무료인가요?

네 — “블로킹과 논블로킹 I/O” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C Academy 강의 전체를 잠금 해제할 수 있습니다. C Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“블로킹과 논블로킹 I/O”에서 뭘 배우나요?

이벤트 루프가 중요한 이유를 알아보세요. 브라우저에서 직접 실행하는 실습 코드로 C Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

C Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 C Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.

“블로킹과 논블로킹 I/O” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 C Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 C Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 블로킹과 논블로킹 I/O
  2. epoll 설정하기
  3. 이벤트 루프
  4. 간단한 에코 서버
← C Academy(으)로 돌아가기