E/S bloqueante versus não bloqueante
Entenda por que os laços de eventos são importantes.
E/S bloqueante versus não bloqueante é uma aula grátis de C Academy no CoddyKit. Esta é a aula 1 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de C Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de C Academy inclui 4 aulas no total.
O que significa bloqueante
Uma chamada de sistema bloqueante suspende a thread chamadora até que a operação possa prosseguir. Quando o senhor chama recv() em um soquete sem dados, o núcleo estaciona sua thread até a chegada dos bytes.
É simples raciocinar sobre isso: uma conexão, uma thread, código linear. O custo aparece quando é necessário atender milhares de clientes ao mesmo tempo.
ssize_t n = recv(fd, buf, sizeof buf, 0);
/* thread sleeps here until data or error */
if (n > 0) handle(buf, n);O problema de escalar
Com E/S bloqueante, um cliente travado bloqueia toda a thread. A solução clássica é usar uma thread (ou processo) por conexão.
Isso funciona até certo ponto, mas 10.000 threads significam 10.000 pilhas, muita troca de contexto e sobrecarga do escalonador. Esse é o famoso problema C10k, que levou os servidores a adotar arquiteturas orientadas a eventos.
Modo não bloqueante
Um soquete não bloqueante nunca dorme. Se uma chamada não puder ser concluída imediatamente, ela retorna -1 na hora e define errno como EAGAIN ou EWOULDBLOCK.
Agora seu código é responsável por tentar novamente mais tarde. Isso permite que uma única thread gerencie muitos soquetes sem ficar presa em nenhum deles.
ssize_t n = recv(fd, buf, sizeof buf, 0);
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
/* no data right now, try again later */
}Definindo O_NONBLOCK com fcntl
O senhor muda um descritor para o modo não bloqueante adicionando a flag O_NONBLOCK com fcntl(). Sempre leia primeiro as flags atuais e depois faça OR com o bit, para não eliminar outras configurações.
Esse mesmo auxiliar é usado igualmente em soquetes de escuta, soquetes de clientes aceitos e pipes.
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);
}Tratando leituras parciais
A E/S não bloqueante torna as operações parciais normais. Um recv() pode retornar menos bytes do que o solicitado, e send() pode aceitar apenas parte do seu buffer.
É necessário acompanhar quanto foi enviado ou recebido e continuar a partir desse ponto. Nunca presuma que uma chamada move todos os bytes.
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;
}Espera ocupada é um erro
A maneira ingênua de usar soquetes não bloqueantes é percorrer todos eles em um laço, tentando novamente sem parar. Essa espera ocupada consome 100% da CPU mesmo quando nada acontece.
O que realmente queremos é perguntar ao núcleo: "informe quais descritores estão prontos e deixe-me dormir até lá". É exatamente isso que a notificação de prontidão oferece.
Notificação de prontidão
A multiplexação de E/S permite que uma thread espere por muitos descritores ao mesmo tempo e acorde somente quando pelo menos um estiver pronto. O núcleo faz o monitoramento por nós.
As interfaces clássicas são select() e poll(). Elas funcionam, mas reexaminam todos os descritores a cada chamada, o que fica caro em grande escala.
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
select(fd + 1, &rfds, NULL, NULL, NULL);Por que select e poll não escalam
Tanto select() quanto poll() são O(n): cada chamada passa o conjunto completo de descritores ao núcleo, que examina todos; depois o senhor examina todos novamente para encontrar os que estão prontos.
select() também fica limitado por volta de FD_SETSIZE (frequentemente 1024). Para milhares de conexões, essa sobrecarga predomina.
Entra o epoll
epoll é a solução escalável do Linux. O senhor registra o interesse em um descritor uma vez, e o núcleo mantém uma estrutura de dados interna que acompanha a prontidão.
Cada espera retorna apenas os descritores que realmente estão prontos, portanto o custo cresce com as conexões ativas, não com o total de conexões. Isso o torna aproximadamente O(1) por evento pronto.
int epfd = epoll_create1(0);
/* register fds once, then wait for ready events */Não bloqueante mais epoll
epoll e soquetes não bloqueantes trabalham em conjunto. epoll informa que um descritor está pronto; as chamadas não bloqueantes permitem esvaziá-lo sem nunca dormir.
O senhor deve sempre definir O_NONBLOCK nos soquetes entregues ao epoll. Caso contrário, uma ativação espúria ou uma leitura parcial poderá bloquear sua única thread do laço de eventos.
set_nonblocking(conn_fd);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);O modelo mental
Imagine o servidor como um laço: bloqueie em epoll_wait(), receba uma pequena lista de descritores prontos, faça trabalho não bloqueante em cada um e repita.
A thread dorme quando está ociosa e acorda apenas para trabalho real. Agora uma thread pode atender dezenas de milhares de conexões com eficiência.
Verificação rápida
Teste sua compreensão sobre soquetes não bloqueantes.
Recapitulação
A E/S bloqueante é simples, mas ocupa uma thread por conexão, o que falha em grande escala. A E/S não bloqueante retorna imediatamente com EAGAIN em vez de dormir.
Consultar soquetes em um laço apertado desperdiça CPU, por isso usamos a notificação de prontidão. select/poll são O(n); epoll escala para muitos milhares de conexões. Agora configuraremos o epoll.
Perguntas Frequentes
A aula “E/S bloqueante versus não bloqueante” é grátis?
Sim — o texto completo de “E/S bloqueante versus não bloqueante” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de C Academy, atualize para CoddyKit PRO. O curso de C Academy inclui 4 aulas no total.
O que vou aprender em “E/S bloqueante versus não bloqueante”?
Entenda por que os laços de eventos são importantes. Você pratica C Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar C Academy?
Nenhuma experiência prévia é necessária. C Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 1 de 4.
Quanto tempo leva a aula “E/S bloqueante versus não bloqueante”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de C Academy?
Sim. Cada aula de C Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- E/S bloqueante versus não bloqueante
- Configurando epoll
- O laço de eventos
- Um servidor de eco simples