0Pricing
C Academy · Aula

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

  1. E/S bloqueante versus não bloqueante
  2. Configurando epoll
  3. O laço de eventos
  4. Um servidor de eco simples
← Voltar para C Academy