This is a creation in Article, where the information may have evolved or changed.
Xu Xiwei: Let's start by introducing Ecug, starting in 07, the first in the Pearl River Delta Zhuhai, Guangzhou, Shenzhen, in the Pearl River Delta, the first is the community of Erlang. About 10 when the name changed to real aging cloud computing Group, the earliest time is not limited to Erlang, but there will be a variety of languages such as Haskell, Scala, etc., in fact, there is no limit, as long as it is halfway interspersed with the back-end development operation of the practice can be, Later, we formally renamed the Cloud Computing group. , the scope of expansion is quite large to the country, basically Beijing, the Yangtze River delta has been held. So it should be said that today insisted on also almost 8 years, a total of 9, in 07 when the 2 session. This is the history of ECUG. Nanjing is the first time to run this Congress, my university is in Nanjing read. This year I think for a long time, we hope to be able to put this spark in all cities can be ignited, so this year to choose Nanjing such a place for me more special significance.
I started my topic. Actually, this topic I actually said in the ecug of Hangzhou, but it was quite graceful at that time, actually I had realized the problem of Erlang's programming style, but the solution was not thorough, so I went back to this topic today, What is the difference between the go and the Erlang concurrency model in a relatively detailed way? Because basically I know people with ECUG history are confused, why I go from Erlang to goo.
The lightweight process model I mentioned very early on, from the time I first advocated Erlang, the concept was introduced, what is the lightweight process model? Very simple is two, one is to encourage the use of synchronous IO Write program logic, the 2nd is to use as many concurrent processes to enhance the IO concurrency capability. This is not the same as the asynchronous IO concurrency model, even if you are single-threaded and can do high concurrency.
The core idea of all lightweight process concurrency models is the same, first making each lightweight process less resource-intensive, so you can create millions-level concurrency, and the only limit on the number of processes that can be created is your memory. The smaller each process resource occupies, the higher the concurrency capability can be generated. As a server, we all know that memory resources are very valuable resources, but they are also very inexpensive in some sense. The second is a lighter switching cost, which is why the process is user-state, this and the function of the call is basically in the same order of magnitude, the switching cost is very very low. However, if the operating system process is at least from the user state to the kernel mentality to the user state switch.
Let's talk about the implementation principle of the lightweight process model, which is quite a lot of people or more concerned. I've talked less about this before, but today we're going to talk a little bit about what the lightweight process is all about. Let's talk about the process, what is the so-called process? In fact, the process is essentially nothing more than a stack plus register state. How does the process switch? is to save the current process register and then modify the register to the Register state of another new process, which is equivalent to switching the stack at the same time, because the stack's position is actually register-maintained (ESP/EBP). This is the concept of process, even if the kernel of the operating system helps you do it in essence. So these things can be done in a user-state, not in the same way. In essence, the call to the function is similar to the one you can think of, because the call to the function also holds the register, but it is relatively small, at least not switching the stack. So essentially it is the cost of this switchover is basically the same as the function call, I have measured, probably is the function call of about 10 times times, basically still in the same order of magnitude category. So after the introduction of the concept of such a lightweight process, what is actually the process of the entire lightweight process physically? The underlying is actually the line Cheng asynchronous Io, and you can think of each thread in the thread pool as a virtual CPU (Vcpus). The number of logical lightweight processes (routine) is usually much larger than the number of physical threads, and each physical thread must have only one routine running at a time, and more routine are waiting. But this waiting for the routine there are two, one is waiting for Io, that is, I put the CPU to him also can not do live, there is an IO operation has been completed, or itself does not wait for any preconditions, in short, is able to participate in scheduling. If a physical thread (Vcpus) its routine is active or because IO triggers a dispatch, the thread (Vcpus) is given out, this time a new routine can be run on it, that is, from the waiting and can meet the schedule routine participate in scheduling, Select a routine according to a priority algorithm. So the principle of lightweight process scheduling is this, it is a user-state thread, and then there is a non-preemptive scheduling mechanism, the timing is mainly triggered by the IO operation. Io operation occurs, the function of IO operation is implemented as follows: First initiate an asynchronous IO request, after initiating the routine state is set to wait for IO to complete, and then to give the CPU, this time also triggered the dispatch scheduler, this time the dispatcher will see if there are people waiting to dispatch, With it, you can switch the past. Then when the IO event is completed, the IO will usually have a callback function as the completion of the IO event notification, which will be taken over by the scheduler, what exactly? It is very simple to put this IO operation belongs to theRoutine is set to ready and can participate in scheduling. Because the state is just waiting for IO, even if it is dispatched to it, there is no way to do things. And ready is to let the routine participate in scheduling. There is also a situation is routine actively transfer CPU, in this case the state of routine in the switch is still ready, any time can be cut to it. These are basically non-preemptive scheduling inside the most basic of several scheduler trigger conditions: IO operations, IO completion events, the active transfer of CPU. But in fact, in the user-state thread can also implement preemptive scheduling, the practice is very simple, the scheduler up a timer, this timer scheduled to start a scheduled task, this timer task check each is executing in the state of the routine, The discovery takes up the CPU time to be able to let it voluntarily give up the CPU, this can realize the preemptive type dispatch. So even in the user state, it can fully implement the operating system process scheduling all the things done. This is how lightweight processes are implemented.
The next question is, what's the difference between Erlang and go? These two are not all concurrent models of lightweight processes? It should be said that their basic philosophy is quite similar, but there are very big differences in detail, not a little bit of difference. The main difference is: The first attitude to the lock is different, the second attitude to asynchronous IO is not the same, the third is not the main details, but the important details, the message mechanism of the two are not the same.
First of all, the attitude to the lock, Erlang is very offensive to the lock, it is believed that the variable can be largely avoided lock, Erlang thought the lock has a great mental burden, so there should be no lock. The idea of Go is that locks do have a great mental burden, but locks are largely avoided. We first macro look at why the lock is to avoid the first server is a shared resource, is a lot of users in use, not for someone to use, so the server itself is a shared resource, once there is concurrency is these concurrent requests in the grab this shared resources. We know that once someone shares the state and each other to change it, this time must be locked, this is not the technical implementation of the details for the transfer, of course, this analysis is from a macro point of view, I will also talk about the technical details, to talk about why the lock can not be avoided.
Why isn't Erlang avoiding locks? As we have just said, it is essentially because of the existence of a process mailbox, and Erlang's server is a single process (the execution body), so there is no concurrency on a regular, so there is no need to lock, but once the asynchronous IO is introduced there will be pseudo concurrency. Since it is a single process, it is impossible to really have concurrency, but if we consider the Erlang process to be an vcpus, because there are requests not being completed, there are many concurrent requests running on the same vcpus. This may occur when a request needs to be temporarily occupied by a resource that cannot be freed, and there are mutually exclusive behaviors. Once such behavior is bound to have a lock, although the lock is not the implementation of the operating system but its own implementation, the specific may be reflected in something like Busyflag, which is actually a lock. All the features of the lock, such as forgetting to release it, the entire server is suspended, and its behavior is exactly the same as all locks. Some people will say I do not have an operating system lock, it is true that single-threaded program will not have the operating system lock, but there is no doubt that our code is actually locked.
So, on the issue of attitude to locks, Erlang tries to avoid locks, but essentially simply throws the lock problem to the user. And go chooses to accept the fact that the lock cannot be avoided.
Let's look at the attitude to asynchronous IO again. Go believes that there should be no asynchronous IO code in any case. While Erlang is not very pure from the lightweight process concurrency model, it does not repel asynchronous IO, is a hybrid, asynchronous IO programming coupled with a lightweight process model, and the mixed result is that Erlang programming, once asynchronous IO is used, is actually more than the mental burden of simple asynchronous IO programming.
The last detail is the second most important concept I've just talked about, which is the process mailbox for Erlang, all messages sent to the Erlang process are sent to the process mailbox, and Erlang provides the meta-language for sending and receiving messages from the mailbox. Go provides communication facilities such as channel, which can be easily created and then used for process communication. By contrast, the message mechanism of GO is more lightweight. Message Queuing and processes are completely separate facilities.
So the last question is, is it possible to implement the concurrency model of go in Erlang? It is easy to implement the concurrency model of Erlang in go, but would you like to implement the concurrency model of go in Erlang? In principle, No. Because the process is not shared in Erlang, this is the most important basis for his opposition to the lock. Process cannot share state, so do not lock, but in fact I think this is the biggest problem, why? Since Erlang receives the request there is no way to create a sub-executor and then let it handle a specific request without having to take care of it. However, Erlang inside the process is not shared state, you have to change the server state must use asynchronous IO, the thing to do and then throw the message to the server told him you changed the state. By changing the server state through the message, this cost is relatively large, and it brings a lot of problems. So I think it's a bad idea for Erlang to change the state of the message, and it doesn't change anything in a big circle. Of course, if I had to do the concurrency model of go in Erlang, it would require an castration of Erlang, and if we let Erlang's server be stateless, we could implement the concurrency model of go. What kind of server is stateless? It may be easy to think of a PHP server. It gives state to all external storage services, which are maintained by the storage service. If the server of Erlang is stateless, it is possible to implement the concurrency model of go, because all States are modified by external storage. However, the Erlang programmer must be very sad, and it seems that the Erlang language does not bring any substantial benefits. So my conclusion is, it's time to give up Erlang.
This is the content of my speech, thank you all!