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