While I was learning Elixir basics, I was confused about how to manage process registration and discovery.
I got tons of ideas from the bookElixir in Action by Saša Juric.
Here is my study note on it.
There are multiple ways to manage processes.
1. no registration; remember the pid
- We need to remember the pid that is returned when a genserver process is started.
- We can create as many processes as we want from the same module.
- We need to be aware that a pid will be changed when a process is terminated and recreated.
defmoduleMyApp.HelloServerdouseGenServerdefstart_link(id)doGenServer.start_link(__MODULE__,id)enddefhello(pid)doGenServer.call(pid,:hello)end@impltruedefinit(id)do{:ok,%{id:id}}end@impltruedefhandle_call(:hello,_from,state)do{:reply,"hello",state}endendiex>{:ok,pid}=MyApp.HelloServer.start_link(123){:ok,#PID<0.123.0>}iex>MyApp.HelloServer.hello(pid)"Hello"
2. using module name as local alias
- This is suitable when we need only one process from a module.
- Generally, we just use the module name as a local alias.
defmoduleMyApp.HelloServerLocalNamedouseGenServerdefstart_link(id)doGenServer.start_link(__MODULE__,id,name:__MODULE__)enddefhellodoGenServer.call(__MODULE__,:hello)end@impltruedefinit(id)do{:ok,%{id:id}}end@impltruedefhandle_call(:hello,_from,state)do{:reply,"hello",state}endendiex>MyApp.HelloServerLocalName.start_link(123){:ok,#PID<0.205.0>}iex>MyApp.HelloServerLocalName.hello()"Hello"iex>MyApp.HelloServerLocalName.start_link(123){:error,{:already_started,#PID<0.205.0>}}
3. using dynamic tuple as local alias (BAD)
When we want to register multiple processes, we may get tempted to generate an atom dynamically (I did); however it is not a good practice.
Erlang has a limit on the number of atoms we can create. Also atoms are not garbage-collected.
defmoduleMyApp.HelloServerDynamicNamedouseGenServerdefprocess_name(id)doString.to_atom("#{__MODULE__}_#{id}")enddefstart_link(id)doGenServer.start_link(__MODULE__,id,name:process_name(id))enddefhello(id)doGenServer.call(process_name(id),:hello)end@impltruedefinit(id)do{:ok,%{id:id}}end@impltruedefhandle_call(:hello,_from,state)do{:reply,"hello",state}endendiex>MyApp.HelloServerDynamicName.start_link(123){:ok,#PID<0.164.0>}iex>MyApp.HelloServerDynamicName.hello(123)"Hello"iex>:erlang.system_info(:atom_limit)1048576iex>:erlang.system_info(:atom_count)15849iex>(1..99)|>Enum.each(fnx->MyApp.HelloServerDynamicName.start_link(x)end):okiex>:erlang.system_info(:atom_count)16115
4. using Registry and via tuple
- We can use
via_tuple
in place of pid. - By using a composite key, many processes can be registered from the same module without creating extra atoms.
- Obviously, a registry process needs to be started before the registration.
defmoduleMyApp.ProcessRegistrydodefvia_tuple(key)whenis_tuple(key)do{:via,Registry,{__MODULE__,key}}enddefwhereis_name(key)whenis_tuple(key)doRegistry.whereis_name({__MODULE__,key})enddefstart_link()doRegistry.start_link(keys::unique,name:__MODULE__)endenddefmoduleMyApp.HelloServerViaTupledouseGenServerdefvia_tuple(id)doMyApp.ProcessRegistry.via_tuple({__MODULE__,id})enddefwhereis(id)docaseMyApp.ProcessRegistry.whereis_name({__MODULE__,id})do:undefined->nilpid->pidendenddefstart_link(id)doGenServer.start_link(__MODULE__,id,name:via_tuple(id))enddefhello(id)doGenServer.call(via_tuple(id),:hello)end@impltruedefinit(id)do{:ok,%{id:id}}end@impltruedefhandle_call(:hello,_from,state)do{:reply,"hello",state}endendiex>MyApp.ProcessRegistry.start_link(){:ok,#PID<0.421.0>}iex>MyApp.HelloServerViaTuple.start_link(123){:ok,#PID<0.164.0>}iex>MyApp.HelloServerViaTuple.hello(123)"Hello"iex>MyApp.HelloServerViaTuple.whereis(123)#PID<0.164.0>
5. using global alias
- A cluster-wide lock is set so the processes can be shared across multiple nodes.
defmoduleMyApp.HelloServerGlobalNamedouseGenServerdefwhereis(id)docase:global.whereis_name({__MODULE__,id})do:undefined->nilpid->pidendenddefregister_process(pid,id)docase:global.register_name({__MODULE__,id},pid)do:yes->{:ok,pid}:no->{:error,{:already_started,pid}}endenddefstart_link(id)docasewhereis(id)donil->{:ok,pid}=GenServer.start_link(__MODULE__,id)register_process(pid,id)pid->{:ok,pid}endenddefhello(id)doGenServer.call(whereis(id),:hello)end@impltruedefinit(id)do{:ok,%{id:id}}end@impltruedefhandle_call(:hello,_from,state)do{:reply,"hello",state}endendiex>MyApp.HelloServerGlobalName.start_link(123){:ok,#PID<0.205.0>}iex>MyApp.HelloServerGlobalName.hello(123)"Hello"iex>MyApp.HelloServerGlobalName.start_link(123){:error,{:already_started,#PID<0.205.0>}}
Starting a cluster
- Open two iex shells
- Turn BEAM instances (iex) into nodes
- Make a cluster connecting those notes
iex--snamenode1@localhostiex(node2@localhost)>_
iex --sname node2@localhostiex(node2@localhost)> Node.connect(:node1@localhost)true
We can confirm processes are shared in two iex shells (BEAM instances).
Finally
With this much info, I feel confident about handling processes. I'll keep on updating the post as soon as I learn something new that is relevant.
Happy coding!
Links
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse