concorrencia101
  • Introdução
  • First things first
  • Agradecimentos
  • Parte I - Concorrência no sistema operacional
    • O que é o programa no sistema operacional
    • Escalonador preemptivo de tarefas
    • Uma nota sobre escalonamento cooperativo
    • Propriedades de um processo
    • Clone de processo (forking)
    • Clone leve de processo (thread)
    • Todo processo tem uma thread principal
    • Uma nota sobre paralelismo
    • Principais desafios em cenário de concorrência
      • Race condition
      • Sincronização com locks
      • Modelo de atores
    • E o I/O?
      • Latência de CPU vs Latência de I/O
      • Chamadas bloqueantes
      • Chamadas não-bloqueantes
      • Assincronismo e escalonamento cooperativo
    • Vamos colocar em prática...
  • PARTE II - Concorrência em diferentes linguagens
    • Definindo ambientes de execução
    • Concorrência em C
      • Forking de processos
      • Threads
      • Race condition e sincronização de threads com mutex
      • Desafios com o uso de threads
      • Thread Pool em C
      • Green threads
      • Modelo de Atores
      • Trabalhando com I/O
    • Concorrência em Ruby
      • Forking de processos
      • Threads
      • Race condition, YARV, GVL e paralelismo em Ruby
      • Modelo de Atores
      • Trabalhando com I/O
Powered by GitBook
On this page
  1. Parte I - Concorrência no sistema operacional
  2. E o I/O?

Chamadas não-bloqueantes

PreviousChamadas bloqueantesNextAssincronismo e escalonamento cooperativo

Last updated 5 months ago

O sistema operacional fornece um recurso muito interessante de chamadas não-bloqueantes em I/O. Desta forma, o programa recebe imediatamente um file descriptor que representa o I/O solicitado, e depois recorre a recursos do sistema operacional para verificar quando o recurso está pronto. Assim, o programa não fica bloqueado.

Esta é a definição de assincronismo, ou I/O assíncrono.

Em Linux, chamadas de sistema como select ou epoll permitem controlar I/O assíncrono. Com select o programa verifica quais descritores estão prontos, enquanto que no epoll, o SO notifica o programa através de uma fila quais descritores estão prontos.

Também não vamos por enquanto entrar nos detalhes do uso de select ou epoll, isto fica numa seção futura neste guia quando entrarmos na parte de implementação. Aqui, é importante entendermos os conceitos.

De qualquer forma, o programa, seja ele qual for, não precisa usar multi-thread para lidar com I/O - a não ser que realmente queira. Com apenas uma thread, é possível criar um "loop" que fica verificando no SO quais chamadas ficaram prontas.

Vamos, em pseudocódigo, escrever um loop com este propósito:

fun main():
    # Lista de descritores de arquivos
    
    input_fds  = [fd1, fd2, fd3]  # Descritores para monitorar leitura
    output_fds = [fd4]            # Descritores para monitorar escrita
    error_fds  = []               # Descritores para monitorar erros

    while true:
        # Chama a função select e espera até que algum descritor esteja pronto
        ready_read, ready_write, ready_error = 
	        select(input_fds, output_fds, error_fds)

        # Verifica os descritores prontos para leitura
        for fd in ready_read:
            data = read(fd)
            print("Dados lidos de fd:", fd, "->", data)

        # Verifica os descritores prontos para escrita
        for fd in ready_write:
            write(fd, "Mensagem de teste")
            print("Dados escritos em fd:", fd)

        # Verifica os descritores com erro
        for fd in ready_error:
            print("Erro detectado no descritor:", fd)

Enfim, a ideia aqui é ilustrar como seria um loop de eventos hipotético, que tanto ouvimos falar:

  • iniciar loop

  • passar para o select (ou epoll) a lista de descritores que queremos monitorar

  • ler os descritores que estão prontos para leitura

  • escrever nos descritores que estão prontos para escrita

  • repetir o loop infinitamente

Repare que, ao termos um loop assíncrono de eventos, é extremamente importante toda e qualquer chamada ser não-bloqueante, caso contrário, se tivermos ao menos uma chamada bloqueante, o loop todo ficará bloqueado, e consequentemente a thread ficará bloqueada.

Lembrem-se, com I/O assíncrono nunca podemos bloquear o loop. Toda chamada no I/O deve ser assíncrona.