Equipe: Gabriela Cunha (gcs@cin.ufpe.br), Roberto Souto Maior (rsmbf@cin.ufpe.br)

Tema

Incluir mecanismo de concorrência, inspirado no de Erlang, na linguagem Funcional 2

Motivação

Concorrência é a habilidade de diferentes funções executarem em paralelo, sem afetar umas as outras, a não ser quando programadas explicitamente para isso. Cada atividade concorrente em Erlang é chamada de processo. A única forma de processos interagirem uns com os outros é através de troca de mensagens, onde dados são enviados de um processo para outro.

O modelo de concorrência de Erlang foi construído desde o seu início. Com processos leves, é usual ter centenas de milhares, ou até milhões, de processos executando em paralelo, e ainda assim sem muito consumo de memória. A habilidade do sistema de execução de escalar concorrência a esses níveis afeta diretamente a forma como os programas são desenvolvidos, diferenciando Erlang de outras linguagens concorrentes.

Muitas empresas usam Erlang em seus sistemas, principalmente por conta da escalabilidade do seu mecanismo de concorrência:

Descrição

Em Erlang um processo pode ser criado através de duas variações da função spawn:

É importante mencionar que a função spawn retorna o identificador, Id, do processo criado ao ser executada.

Como mencionado anteriormente, processos se comunicam através de troca de mensagens, onde o conteúdo das mensagens pode ser qualquer estrutura de dados da linguagem. Cada processo tem uma caixa de mensagens associada ao mesmo. A medida que um processo recebe novas mensagens elas chegam à caixa de mensagens e são organizadas em uma fila, isto é, a primeira mensagem que chega será a primeira que se tentará ler.

Mensagens são enviadas utilizando a estrutura Id ! Msg, onde Id é o identificador do processo que deve receber a mensagem, ! é o operador de envio de mensagem e Msg é a mensagem que deve ser enviada para o processo Id.

Para receber uma mensagem, é utilizada a estrutura receive. Esta estrutura utiliza casamento de padrão para mapear o conteúdo das mensagens e tem o seguinte formato:

receive
Pattern1 when Guard1 -> exp11, .., exp1n;
Pattern2 when Guard2 -> exp21, .., exp2n;
...
Other -> expn1, .., expnn
end

Esta estrutura irá sempre tentar consumir a primeira mensagem na fila da caixa de mensagens do processo. Basicamente, o conteúdo da mensagem em questão passará por um casamento de padrão com cada padrão da estrutura receive: É importante mencionar que quando não há mensagens para serem lidas, ou nenhuma das mensagens disponíveis casa com algum padrão da estrutura receive, o processo será travado (suspenso) até ocorrer a chegada de uma mensagem que case com algum dos padrões da estrutura.

Exemplo

Exemplo de spawn

O exemplo acima contém um módulo, chamado echo, com 2 funções: go() e loop(). Ao chamarmos a função go(), um processo é criado utilizando a função spawn. Este processo irá executar a função loop() de maneira concorrente a execução de go(), que por sua vez se dará no processo inicial. Como parâmetros na criação do processo foram passados o módulo echo, a função loop() e lista vazia, pois a função loop() não recebe nenhum parâmetro. O processo responsável por executar loop() será inicialmente suspenso, porque esta função contém uma cláusula receive, mas sua caixa de mensagens está vazia.

O processo inicial, ainda executando em go(), usa o Id (Pid) retornado pela chamado de spawn, para enviar uma mensagem para o processo que está executando loop(). Esta mensagem contém uma tupla com o Id do processo, retornado por self(), e uma string "Hello". A função self(), é uma função pré-definida responsável por retornar o Id do processo sendo executado no momento. Após enviar a mensagem, este processo será suspenso em sua cláusula receive, dado que sua caixa de mensagens está vazia.

Como o processo responsável por executar loop() recebe uma mensagem, ele é retomado, e tenta, em ordem, casar a mensagem recebida com alguma das suas cláusulas. Neste exemplo, o padrão {From, Msg} irá casar com a mensagem recebida e o corpo desta cláusula será executado. Ou seja, este processo irá enviar uma resposta para o processo inicial com com uma tupla contendo o seu Id, retornado por self(), e o conteúdo da mensagem recebida inicialmente, "Hello", concatenado com " world!". Finalmente, a execução deste processo irá chamar a função loop() recursivamente, para evitar que a execução do processo termine. A execução recursiva desta função, irá fazer com que a função loop() trave novamente na cláusula receive.

Em paralelo, o processo inicial irá ter recebido a mensagem, enviada pelo processo executando loop(), e poderá retomar sua execução. A mensagem recebida irá casar com o único padrão de sua cláusula receive e portanto o seu corpo poderá ser executado, levando a chamada de io:format() com o conteúdo da mensagem recebida ("Hello world!"). Em seguida, a execução desta função envia o booleano false para o outro processo.

Finalmente, a mensagem recebida pelo segundo processo casa com o segundo padrão da cláusula receive de loop(), e retorna true, terminando normalmente.

Referências