• Erlang’s concurrency is based on the messaging and Actor models

  • In Erlang, Concurrncy refers to having many independent actors running but not requiring them to run at the same time, while Parallelism refers to multiple actors running at the same time

  • Erlang is very reliable, so it takes the most radical approach and forbids sharing memory between processes

  • Because shared memory complicates the problem by leading to inconsistent states in the system after a crash

  • Unlike shared memory, processes can only communicate by sending messages, and all message data is replicated. It’s less efficient, but safer

  • When something goes wrong in one part of the system, causing data corruption, that part should die as soon as possible to prevent the error and bad data from spreading to the rest of the system

  • Erlang implements processes in the VM so that implementers have complete control over optimization and reliability

  • An Erlang process takes about 300 words of memory and takes only a few minutes to create

  • To manage all the processes created by the program, the VM starts a thread for each core to act as a scheduler.

  • Each scheduler has a Run Queue, which is a list of Erlang processes to which each process is assigned a small elapsed time slice

  • When a scheduler has too many tasks in its run queue, some of the tasks are migrated to other queues. This means that every Erlang VM does load balancing and the programmer doesn’t have to care

  • Erlang concurrent programming requires three primitives: create a spawn process, send a message, and receive a message

  • A process is a function in Erlang. A process runs a function, and when it’s done, it’s usually gone

  • To start a new process, use the function spawn/1 provided by Erlang, which takes a function as an argument and runs it

    > F = fun() -> 2 + 2 end.
    > spawn(F).
    <0.82.0>
    Copy the code
  • The return value of spawn/1 (<0.82.0>) is called the process identifier and is usually written as PID, PID, or PID

  • Pid is an arbitrary value used to represent a process that exists (or existed) at some point in time during the running of the virtual machine

  • The PID can be used as the address for inter-process communication

  • In the above example, we cannot get the return value of function F. We can only get its PID. Because the process doesn’t return anything

  • Using BIF’s self/0 function, you can return the PID of the current process

  • Erlang’s messaging primitive — operator! , also known as the bang symbol. The left-hand side of this operator is a PID, and the right-hand side can be any Erlang data item. The data item is sent to the process represented by the PID on the left, which can then access it

    > self() ! hello.
    Copy the code
  • The message is placed in the receiving process’s mailbox, but is not read. The second Hello that appears in the above example is the return value of this send function. This means that you can send the same message to multiple processes in the following way

    > self() ! self() ! double
    Copy the code
  • Messages in the process mailbox are stored in the order they are received, and each time a message is read, it is fetched from the mailbox

    > flush().
    Shell got hello
    Shell got double
    Shell got double
    ok
    Copy the code
  • The Flush /0 function is just a quick way to print the received message

  • Use the receive expression to receive messages. Receive syntax and case… Of is very similar. In fact, their pattern-matching parts work exactly the same, except that in the Receive mode variables are bound to the received message rather than the expression between case and of. Receive expressions can also have guard statements

    receive
        Pattern1 when Guard1 -> Expr1;
        Pattern2 when Guard2 -> Expr2;
        Pattern3 -> Expr3
    end
    Copy the code
  • The only way to know if the process received the message is to have it send a response. Our process must add our PID to the message if it needs to know who to send the response to.

  • In Erlang, we do this by packaging the process PID in a tuple, otherwise the messages are all anonymous. The result is a Message like {Pid, Message}

  • Let’s write a dolphin program to show sending and receiving messages

    -module(dolphins).
    -compile(export_all).
    
    dolphin() ->
        receive
            {From, do_a_flip} ->
                From ! "How about no?",
                dolphin();
            {From, fish} ->
                From ! "So long and thanks for all the fish!";
            _ ->
                io:format("Heh, we're smarter than you humans.~n"),
                dolphin()
        end.
    Copy the code
    Eshell > Dolphin = spawn(dolphins, Dolphin, []). <0.85.0> > Dolphin! {the self (), do_a_flip}. {< 0.78.0 >, do_a_flip} > Dolphin! {self(), unknown_message}. Heh, we{<0.78.0>,unknown_message} > Dolphin! {self(), fish}. {<0.78.0>,fish} > flush(). Shell got "How about no? Shell got "So long and thanks for all the fish!" okCopy the code
  • In the above test, a new process creation function, spawn/3, was introduced. Instead of taking just one function as an argument, the spawn/3 function takes three arguments: module, function, and function argument

  • If processes and actors are just functions that send and receive messages, it doesn’t do much good. To get the most out of it, you need to hold state in the process

  • With the help of recursive functions, all the state of a process can be stored in the parameters of recursive functions

  • If you use message sending and receiving directly, the programmer needs to know which protocol each process itself is using. It’s a meaningless burden.

  • A good way to hide messages is to use functions to handle receiving and sending messages

    store(Pid, Food) ->
        Pid ! {self(), {store, Food}},
        receive
            {Pid, Msg} -> Msg
        end.
    Copy the code
  • It is also common in Erlang to add a start/1 function in modules to hide process startup

    start() -> spawn(? MODULE, dolphin, []).Copy the code
  • ? MODULE is a macro whose value is the name of the current MODULE

  • Receive can handle timeouts using the after clause

    receive
        Match -> Expression1
    after Delay ->
        Expression2
    end
    Copy the code
  • When no message matching the Match pattern is received after the Delay(milliseconds) has elapsed, the after part is executed

  • In fact, after can receive an infinity atom in addition to a millisecond value

  • In most languages, exceptions use try… Catch is handled within the program execution stream

  • One problem with this common approach is that you either have to handle exception errors at every level of normal code logic, or you have to push the burden of error handling all the way to the top level of your program. This will catch all errors, but you will never know why the error occurred

  • Erlang supports another level of exception handling in addition to the usual exception handling patterns. You can move the exception handling logic out of the normal flow of your program and into another concurrent process. This approach makes the code cleaner and only considers the “normal case”

  • A link is a special relationship between two processes. When this relationship is established between two processes, if one process dies due to an unexpected throw, error, or exit, the other process dies as well, binding the separate lifetimes of the two processes into an associated lifetime

  • This is a very useful concept from the point of view of failing as quickly as possible to prevent errors from spreading. If a process crashes due to an error, but the dependent process continues to run, all of these dependent processes must deal with dependency loss. Letting them die and then restarting the entire process group is usually an acceptable alternative. Links do just that

  • Another native function in Erlang, link/1, is used to establish a link between two processes and takes the pid of the process as an argument. When called, a link is established between the current process and the process identified by the PID parameter. To remove links, use unlink/1

  • When a link process dies, a special message is sent with information about the cause of death. If the process dies normally (the function completes execution), this message will not be sent

    -module(linkmon).
    -compile(export_all).
    
    myproc() ->
        timer:sleep(5000),
        exit(reason).
    Copy the code
    Eshell
    > c(linkmon).
    > spawn(fun linkmon:myproc/0).
    > link(spawn(fun linkmon:myproc/0)).
      true
      ** exception error: reason
    Copy the code
  • Attention! Links do not stack, and if link/1 is called multiple times between the same two processes, there will only be one link between the two processes, which can be broken with a single unlink/1 call

  • Link (spawn(Function)) or link(spawn(M, F, A)) is not an atomic operation. Sometimes the process dies before the link is successfully established, resulting in unexpected behavior. Therefore, the spawn_link/1-3 function has been added to Erlang. This function takes exactly the same arguments as spawn/1-3, creating a process and linking to it as if link/1 had been used, but this is an atomic call (two operations are merged into one operation and either succeed or fail, nothing else).

    > Spawn_link (fun linkmon:myproc/0). <0.90.0> ** Exception error: reasonCopy the code
  • Error propagation across processes is similar to messaging for processes, but uses a special message called a signal. Exit signals are “secret” messages that automatically act on processes and kill them

  • Links do the job of killing processes quickly, but the quick restart part is missing. To restart a process, you first need to know that it is dead, and a concept called a system process can do the job

  • System processes are normal processes, but they can turn exit signals into normal messages. The process can do this by calling process_flag(trap_exit, true)

    > process_flag(trap_exit, true).
    > spawn_link(fun linkmon:myproc/0).
    > receive X -> X end.
      {'EXIT', < 0.97.0 >, a tiny}Copy the code
  • Maybe killing processes isn’t what you want, maybe you just want to be a stalker. If so, then the Monitor might be just what you want

  • A monitor is a special type of link

  • The monitor is one-way

  • Multiple monitors can be set up between two processes (monitors can be stacked, each with its own identity)

  • A monitor can be used when a process wants to know whether another process is alive or dead, but there is no strong business relationship between the two processes

  • The function that creates the monitor is Erlang :monitor/2, whose first argument is always atomic process and second is PID

    > erlang:monitor(process, spawn(fun() -> timer:sleep(500) end)).
    > flush().
      Shell got {'DOWN'.# Ref < 0.4159903409.3575906310.207444 >, process, < 0.80.0 >, normal}
    Copy the code
  • Whenever a monitored process dies, the monitoring process receives a message in the format of {‘DOWN’, MonitorReference, Process, Pid, Reason}. These references can be used to unmonitor a process

  • Remember! The monitor is superimposed, so it receives multiple DOWN messages. A reference uniquely identifies a DOWN message

  • Like links, the monitor has an atomic function that monitors the process while it is being created: spawn_monitor/1-3

    > {Pid, Ref} = spawn_monitor(fun() -> receive _ -> exit(boom) end end).
    > erlang:demonitor(Ref).
    > Pid ! dir.
    > flush().
    Copy the code
  • In this example, we unmonitored the process before it died, so we could not trace its death message. There is another function, deMonitor /2, that does a little more. The second argument is a list of options. However, only two options are available: info and Flush

    > erlang:demonitor(Ref, [flush, info]).
      false
    Copy the code
  • The INFO option is used to indicate whether a monitor still exists when disarmed. That’s why this call returns false, right

  • The Flush option deletes all DOWN messages in the mailbox

  • Erlang also provides a method for naming processes. By giving the process a name, you can identify a process with an atom instead of an incomprehensible PID. You can use this atomic name to send messages to the process, exactly like pid

  • You can Name a process using the function Erlang :register(Name, Pid). If a process dies, it automatically loses its name. You can also manually unregister a process name using the function unregister/1

  • You can call registered/0 to get a list of all registered processes or use the Eshell command regs() to get more detailed information

  • The PID of a registered process can be obtained using the function whereis/1

  • If a single piece of data can be seen by multiple processes, this is known as a shared state

  • If multiple processes access or modify data at the same time, the information is inconsistent and software errors occur. There is a common term for this: race condition.

  • Race conditions are dangerous because their emergence depends on the timing of events. In almost all existing concurrent and parallel languages, this timing is related to unpredictable factors such as how busy the processor is, where the process is running, and the type of data the program is processing

  • When we actually use Erlang to send and receive messages, we should use a reference (make_ref()) as the unique value to identify the message and use it to ensure that the correct message was received from the correct process

    judge2(Band, Album) ->
        Ref = make_ref(),
        critic ! {self(), Ref, {Band, Album}},
        receive
            {Ref, Criticism} -> Criticism
        after 2000 ->
            timeout
        end.
    
    critic2() ->
        receive
            {From, Ref, {_Band, _Album}} ->
                From ! {Ref, "They are terrible!"}
        end,
        critic2().
    Copy the code
  • And finally remember, there’s a finite number of atoms. Never create atoms dynamically. This means that named processes should be reserved for services that are unique and important in a single VM instance, and that persist for the entire duration of the application. If you need to name processes that are temporary or not unique to the VM, that means you might want to treat them as a group. It would be wise to link them together and let them live and die together rather than try to use dynamic names

The original link