Uma confusão bem comum quando se começa a escrever programas concorrentes em Erlang é quanto ao uso da BIF (built-in function) self()
. Mais especificamente, quanto ao seu retorno.
A BIF self()
é analoga ao this
do Java, por exemplo, que é capaz de responder quem é o objeto contenedor do método atualmente em execução. Semelhantemente, em Erlang, self()
é capaz de dizer quem é o processo contenedor da função atualmente em execução avaliação. Assim, se você chamar self()
no Erlang shell, você vai receber como retorno o Pid
(identificador de processo) do próprio Erlang shell.
Faça um teste no seu Erlang shell:
1> self().
Você deve ter recebido algo semelhante a <0.31.0>
com retorno. Isto porque o Erlang shell nada mais é do que um processo Erlang com um comportamento REPL.
Ok. Agora, o que acontece se você tiver um programa com um único módulo em que há duas funções que trocam mensagens entre dois processos? Qual seria o retorno de self()
nestas duas funções?
Um pequeno exemplo
Vejamos um exemplo bem simples deste caso extraído do livro Erlang Programming:
-module(add_two). -export([start/0, request/1, loop/0]). start() -> process_flag(trap_exit, true), Pid = spawn_link(add_two, loop, []), register(add_two, Pid), {ok, Pid}. request(Int) -> add_two ! {request, self(), Int}, receive {result, Result} -> Result; {'EXIT', _Pid, Reason} -> {error, Reason} after 1000 -> timeout end. loop() -> receive {request, Pid, Msg} -> Pid ! {result, Msg + 2} end, loop().
De maneira bem objetiva, o que este código faz é o seguinte:
1- Quando um processo já existente — que no nosso caso será o próprio Erlang shell — faz uma chamada à função start()
, um novo processo é gerado, tendo como ponto de partida a função loop()
, o seu identificador é associado à variável Pid
e, por fim, ele recebe o apelido add_two
.
2- Todas as vezes que a função request(Int)
é chamada, uma mensagem é enviada para o processo add_two
, para que este some 2 ao número passado como parâmetro e envie o resultado de volta ao processo solicitante.
3- Sempre que o processo add_two
recebe uma nova mensagem, esta é capturada na sentença receive ... end
da função loop()
, que verifica se é um “pedido de soma”, e então, envia o resultado da soma ao processo solicitante, identificado por Pid
.
A confusão
Bem simples mesmo, certo? Então, por que acontece a tal confusão?
A confusão acontece, porque o retorno de self()
não é o mesmo em todas as funções deste módulo. Isto porque a função loop()
, apesar de estar contida no mesmo módulo que as funções start()
e request(Int)
, não está rodando sendo avaliada no mesmo processo que elas estão. A função loop()
está sendo avaliada no processo add_two
, enquanto que start()
e request()
estão sendo avaliadas no primeiro processo — o Erlang shell. Assim, self()
em loop()
retorna um identificador de processo diferente do que retornaria as demais funções.
Quer tirar a prova?
Mais um simples exemplo, só que desta vez, esclarecedor!
Eu adicionei um bocado de “prints” no código que apresentei anteriormente e se você executá-lo agora, terá a prova do que foi discutido. (Tá tudo bem, você não precisa de uma prova a estas alturas do campeonato, mas vai ser divertido.)
-module(add_two). -export([start/0, request/1, loop/0]). start() -> io:format(": start -> self() = ~w~n", [self()]), process_flag(trap_exit, true), Pid = spawn_link(add_two, loop, []), io:format(": start -> Pid = ~w~n", [Pid]), register(add_two, Pid), {ok, Pid}. request(Int) -> io:format(": request -> self() = ~w~n", [self()]), add_two ! {request, self(), Int}, receive {result, Result} -> Result; {'EXIT', _Pid, Reason} -> {error, Reason} after 1000 -> timeout end. loop() -> receive {request, Pid, Msg} -> io:format(": loop/receive -> self() = ~w~n", [self()]), io:format(": loop/receive -> Pid = ~w~n", [Pid]), Pid ! {result, Msg + 2} end, loop().
Após fazer a devida compilação, faz o seguinte teste no Erlang shell:
1- Veja o identificador de processo (aka Pid) do Erlang shell:
1> self().
2- Inicie o processo add_two
:
2> add_two:start().
3- Chame a função request(Int)
:
3> add_two:request(10).
Executou? Comparou os Pids? Viu a diferença de escopo entre as três funções? Pois muito bem, então fica aqui a lição:
“Módulos servem para agrupar funções com um mesmo escopo conceitual, mas nem sempre com o mesmo escopo de processo”
Só uma nota Leandro (realmente não li tudo), confusão é com S.
@Bruno
Erro de digitação mesmo. Se você ler tudo vai ver que em alguns lugares escrevi com “z” e outras com “s”.
De qualquer forma, muito obrigado!
[]s
Falando menos sobre o conteúdo e mais sobre o estilo do post, acho legal esse lance de explanar sobre um pequeno assunto.
Seria esse o estilo AtomicPost? 🙂
Abraço!
– LeoLuz –
@LeoLuz
hehehe… gostei do termo…
Também gosto de posts pequenos, mas nem sempre consigo ser tão conciso. Estou aprendendo a trabalhar assuntos complicados de maneira descomplicada, mais direta, etc. Minha preocupação é não deixar “vacuos”; e também conseguir, de alguma maneira, despertar no leitor o desejo de buscar algo mais sobre o assunto, se aprofundar.
Mas sigo tentando e aprendendo aos poucos, a cada post… 🙂
[]s