Skip to content
Kartik edited this page Feb 17, 2018 · 1 revision
  • Grains of gold at -> https://www.reddit.com/r/erlang/comments/4sogzb/how_do_erlang_microprocesses_work_internally/

  • On erlang message passing -> https://trigonakis.com/blog/2011/05/26/introduction-to-erlang-message-passing/

  • A warning though; this chapter is mostly theoric. If you have a headache, a distaste for programming language history or just want to program, you might be better off skipping to the end of the chapter or skip to the next one (where more practical knowledge is shown.)

  • The communication model (among processes) in Erlang is message passing. No much need to be said about this. Erlang processes share no memory. The way that processes communicate is via (asynchronous) message passing. Every process (even the shell) holds a mailbox queue where incoming messages are placed until received by the process. Message passing is asynchronous because the sending process does not block on send. Sending a message in Erlang is a non-blocking operation that always succeed (more in the next post).

  • Why Message Passing? But why message passing? We are so used to the shared memory model, why changing it? Here are some characteristics that are part of Erlang mostly because of the message passing memory model. Memory independency While executing, no shared memory among processes means that there is no chance that a process will cause a memory corruption to another process. The programmer does not have to worry about data races, data corruption, memory synhronization, etc. Crash independency Processes do not share anything. As a result, the failure (crash) of a process cannot affect another. This is very helpful when builting fault-tolerant and reliable systems (most of the “real-world” systems). Due to this, Erlang inherits the “let it fail” programming approach; if something “bad” happens, let the process fail and handle the process failure instead. Distributed programming Message passing allows for easier distributed programming. Imagine if you want to distribute an application that uses shared memory. To do this, one should either use a message passing solution (such as MPI) or a Distributed Shared Memory system (DSM), that also uses message passing to operate. Why not using message passing in the first place? Especially in Erlang, message passing allows for location transparency(when sending a message there is no difference to the programmer if the receiver resides in the local or a remote node).

  • Erlang vm is called Beam and it is a "process virtual machine" just like JVM

  • Each erlang process is a very light weight process, with very low memory footprint as compared to an OS process

  • No shared memory between erlang processes, only message passing

  • each erlang vm spawns fixed number of linux processes , that is the maximum amount of parallelism one can achieve

  • underlying these processes are pthreads but with no shared memory

  • In Erlang the language, everything is a term. The heap is array of terms. Stack is array of terms inside heap. Registers are terms. Variables are terms

  • Erlang process scheduling - Simplified image: A scheduler is a loop which runs on a fixed CPU core and it either fetches and executes next instruction based on instruction pointer in current process, or switches to next in the queue. Each instruction or a call has a cost, it uses imaginary units called 'reductions' where 1 reduction is approximately one function call, and the rest are derived from this also approximately. As soon as process has been running for certain number of reductions (say 2000 but number may change), it is scheduled out and put to sleep, and next process takes its place and continues running where it left off. This allows some sort of fair scheduling where everyone is guaranteed a slice of time, no matter how busy some processes are.

  • Sending message to a process is a simple operation: Lock the process mailbox (if we're in SMP), copy term being sent to process' own heap, add the resulting term to its mailbox. Unlock.

  • What kind of virtual machine is BEAM (the Erlang VM)?

  • The Erlang VM runs as one OS process. By default it runs one OS thread per core to achieve maximum utilisation of the machine. The number of threads and on which cores they run can be set when the VM is started. Erlang processes are implemented entirely by the Erlang VM and have no connection to either OS processes or OS threads. So even if you are running an Erlang system of over one million processes it is still only one OS process and one thread per core. So in this sense the Erlang VM is a "process virtual machine" while the Erlang system itself very much behaves like an OS and Erlang processes have very similar properties to OS processes, for example isolation. There is actually an Erlang VM, based on the BEAM, which runs on bare metal and is in fact an OS in its own right, see Erlang on Xen.

  • By the way, it is perfectly possible to have systems running millions of Erlang processes and it is actually done in some products, for example WhatsApp.

  • Don't drink too much Kool-Aid: The distinction between concurrency and parallelism is important to make, because many programmers hold the belief that Erlang was ready for multi-core computers years before it actually was. Erlang was only adapted to true symmetric multiprocessing in the mid 2000s and only got most of the implementation right with the R13B release of the language in 2009. Before that, SMP often had to be disabled to avoid performance losses. To get parallelism on a multicore computer without SMP, you'd start many instances of the VM instead.

  • An interesting fact is that because Erlang concurrency is all about isolated processes, it took no conceptual change at the language level to bring true parallelism to the language. All the changes were transparently done in the VM, away from the eyes of the programmers.

  • Fault tolerance in erlang -> Fault-tolerance This leads us on the second type of requirements for Erlang: reliability. The first writers of Erlang always kept in mind that failure is common. You can try to prevent bugs all you want, but most of the time some of them will still happen. In the eventuality bugs don't happen, nothing can stop hardware failures all the time. The idea is thus to find good ways to handle errors and problems rather than trying to prevent them all. It turns out that taking the design approach of multiple processes with message passing was a good idea, because error handling could be grafted onto it relatively easily. Take lightweight processes (made for quick restarts and shutdowns) as an example. Some studies proved that the main sources of downtime in large scale software systems are intermittent or transient bugs (source). Then, there's a principle that says that errors which corrupt data should cause the faulty part of the system to die as fast as possible in order to avoid propagating errors and bad data to the rest of the system. Another concept here is that there exist many different ways for a system to terminate, two of which are clean shutdowns and crashes (terminating with an unexpected error).

  • Here the worst case is obviously the crash. A safe solution would be to make sure all crashes are the same as clean shutdowns: this can be done through practices such as shared-nothing and single assignment (which isolates a process' memory), avoiding locks (a lock could happen to not be unlocked during a crash, keeping other processes from accessing the data or leaving data in an inconsistent state) and other stuff I won't cover more, but were all part of Erlang's design. Your ideal solution in Erlang is thus to kill processes as fast as possible to avoid data corruption and transient bugs. Lightweight processes are a key element in this. Further error handling mechanisms are also part of the language to allow processes to monitor other processes (which are described in the Errors and Processes chapter), in order to know when processes die and to decide what to do about it.

ok

Clone this wiki locally