UNIX Pipes
Para entendermos UNIX pipes, devemos primeiro recapitular o seguinte exemplo:
$ echo 'my precious' > rawcontent.txt
$ base64 < rawcontent.txt
bXkgcHJlY2lvdXMK
O programa
echo
redireciona a o output para o arquivorawcontent.txt
O conteúdo do arquivo é enviado como input para o comando
base64
Note o padrão aqui: temos uma pipeline de transformação de dados, onde o output de um programa é utilizado como input para o próximo programa.
Esta técnica de pipeline é utilizada em sistemas UNIX-like com o operador pipe |
.
UNIX pipelines
Ao invés de escrevemos nossa pipeline em múltiplas linhas, dificultando a legibilidade caso a complexidade aumente, podemos utilizar o operador |
para montar uma sentença única de comandos encadeados:
$ echo 'my precious' | base64
bXkgcHJlY2lvdXMK
Muito melhor, não? E muito provável você já viu isto em algum lugar, por exemplo:
$ ps ax | grep ruby
88327 s002 S+ 0:00.94 docker run -it ruby irb
88330 s002 S+ 0:00.92 /usr/local/bin/com.docker.cli run -it ruby irb
91074 s003 S+ 0:00.00 grep ruby
O output do comando ps
é enviado como input para o comando grep
. Cool, uh? Este pipe é chamado de pipe anônimo, ou anonymous pipe!
Anonymous pipe
Este pipe é chamado de anônimo justamente por não ter nome e ser temporário, pois o fd é criado durante a pipeline e depois é liberado.
Este tipo de IPC utiliza uma comunicação tem as seguintes caracterÃsticas:
One-way, ou seja, a informação trafega apenas e uma única direção
FIFO (first-in, first-out), ou seja, o output é redirecionado para um pipe e enfileirado como input em outro pipe do próximo comando
$ ps ax | grep docker | tail -n 3
62374 s039 S+ 0:05.31 /usr/local/bin/com.docker.cli run -it ubuntu bash
65442 s040 S+ 0:02.93 docker run -it ubuntu bash
65445 s040 S+ 0:02.86 /usr/local/bin/com.docker.cli run -it ubuntu bash
Quando um |
é criado, abre-se um par de file descriptors, uma para escrita e outro para leitura, tal como fizemos na seção anterior com custom fd.
Como a pipe é anônima, ambos file descriptors abertos são utilizados apenas no contexto da pipeline e são automaticamente liberados/fechados quando a pipeline termina.
Apesar de pipes anônimas |
serem utilizadas praticamente em quase tudo, é possÃvel criamos pipes com nomes?
Named pipes
Como o próprio nome diz, named pipes são pipes com nomes. São similares a pipes anônimas; empregam FIFO e são uma forma de IPC de via única (one-way).
A única diferença é que um named pipe é criado de forma explÃcita via comando mkfifo
, onde um arquivo é criado no filesystem e aberto para escrita e leitura.
$ mkfifo myqueue
Um arquivo chamado myqueue
é criado.
Vamos enviar uma mensagem para o pipe com o comando echo
, utilizando redirecionamento de stream >
que vimos na última seção:
$ echo 'my precious' > myqueue
Note que o processo fica bloqueado, Ã espera de algo.
IPC one-way
Por ser uma estrutura de fila FIFO simples e ser utilizado como one-way IPC, o sistema operacional precisa garantir que a mensagem será recebida por outro processo. Por isso o processo escritor, ou writer, fica bloqueado, pois é preciso que outro processo outro processo leitor (reader) para "consumir" a mensagem do pipe.
Em outra sessão do bash, vamos consumir a informação do pipe utilizando o comando cat
:
$ cat myqueue
my precious
Yay!
O mesmo acontece se iniciarmos com o leitor: este fica bloqueado à espera que alguma mensagem chegue no pipe, no caso através de outro processo escritor.
Implementando um simples Background Job com UNIX pipes
Utilizando anonymous pipes e named pipes, podemos explorar a funcionalidade primitiva de um sistema de processamento assÃncrono (background job).
Começamos por definir os componentes:
Um processo leitor, ou consumer, fica infinitamente à espera de mensagens no pipe (fila)
Diferentes escritores, ou publishers, colocam mensagens no pipe de forma assÃncrona
O processo leitor (consumer) recebe cada mensagem no pipe e faz o devido processamento da mensagem
👉 Nosso background job irá fazer a simples tarefa de receber uma mensagem codificada em base64, decodificá-la, e então mostrar no screen (STDOUT).
Consumer
Primeiro, criamos o pipe que representará a "fila" do nosso background job:
mkfifo myqueue
Agora, o consumer fica em loop infinito à espera de mensagens na fila.
Dentro do loop, consome a mensagem, decodifica-a e então mostra no screen:
while true
do
## Fica bloqueado à espera da próxima mensagem na fila
ENCODED=`cat myqueue`
## Quando a mensagem chega na fila, decodifica-a utilizando o comando
## echo e base64 com anonymous pipes
DECODED=`echo $ENCODED | base64 -d`
echo "Mensagem decodificada: $DECODED"
done
E o consumer está pronto. Código final do arquivo consumer.sh
:
#!/bin/bash
## Cria o named pipe
mkfifo myqueue
echo 'Aguardando jobs na fila...'
while true
do
## Fica bloqueado à espera da próxima mensagem na fila
ENCODED=`cat myqueue`
## Quando a mensagem chega na fila, decodifica-a utilizando o comando
## echo e base64 com anonymous pipes
DECODED=`echo $ENCODED | base64 -d`
echo "Mensagem decodificada: $DECODED"
done
Em uma sessão do bash:
$ bash consumer.sh
Aguardando jobs na fila...
E em outra sessão do bash, podemos utilizar vários producers para enviar diversas mensagens codificadas para a fila:
$ echo 'my precious' | base64 > myqueue
$ echo 'pipes are awesome' | base64 > myqueue
Consultando o output na sessão do consumer:
Mensagem decodificada: my precious
Mensagem decodificada: pipes are awesome
Resumo
Que jornada! Nesta seção vimos a utilização de UNIX pipes para IPC, vimos a semelhança e diferença entre anonymous pipes |
e named pipes, bem como a implementação de um sistema de background jobs com pipes.
Agora, é hora de explorar uma forma ainda mais sofisticada de IPC: UNIX Sockets.
Last updated