The following column will introduce Golang coroutine scheduling to you in the golang tutorial column. I hope it will be helpful to friends in need!
Let’s take a look at golang’s coroutine scheduling
Groutine can have a powerful concurrency implementation through the GPM scheduling model. The following explains the goroutine scheduling model.
Go's scheduler has three important structures inside: M, P, G
M: M is an encapsulation of kernel-level threads, and the number corresponds to the real number of CPUs , an M is a thread, and goroutine runs on M; M is a large structure that maintains a lot of information such as small object memory cache (mcache), currently executing goroutine, random number generator, etc.
G: Represents a goroutine, which has its own stack, instruction pointer and other information (waiting channels, etc.) for scheduling.
P: The full name of P is Processor. Its main purpose is to execute goroutine. Each Processor object has an LRQ (Local Run Queue). Unallocated Goroutine objects are stored in the GRQ (Global Run Queue), waiting to be allocated to a certain P in the LRQ. Each LRQ contains several user-created Goroutines. object.
Golang uses a multi-threading model. In more detail, it is a two-level threading model, but it encapsulates system threads (kernel-level threads) and exposes a lightweight coroutine goroutine. (User-level threads) are for users to use, and the scheduling of user-level threads to kernel-level threads is handled by golang's runtime, and the scheduling logic is transparent to the outside world. The advantage of goroutine is that context switching is performed in the complete user state. There is no need to switch between user state and kernel state as frequently as threads, which saves resource consumption.
As seen from the above figure, there are 2 physical threads M, each M has a processor P, and each There is a goroutine running.
The number of P can be set through GOMAXPROCS(), which actually represents the real concurrency, that is, how many goroutines can run at the same time.
The gray goroutines in the picture are not running, but are in the ready state, waiting to be scheduled. P maintains this queue (called runqueue). In
Go language, it is easy to start a goroutine: just go function, so every time a go statement is executed, a
goroutine is added to the end of the runqueue queue. , at the next scheduling point, a goroutine is taken out from the runqueue (how to decide which goroutine to take?) and executed.
When an OS thread M0 is blocked (as shown below), P is running M1 instead. M1 in the picture may be being created or taken out from the thread cache.
When MO returns, it must try to get a P to run the goroutine. Normally, it will get one from other OS threads. P come over,
If it doesn’t get it, it will put the goroutine in a global
runqueue, and then sleep by itself (put in the thread cache). All P will also check global periodically
runqueue and run the goroutine in it, otherwise the goroutine on the global runqueue will never be executed.
Another situation is that the task G assigned by P is quickly completed (uneven distribution), which causes the processor P to be very busy, but other Ps are still busy. There is a task. At this time, if global There is no task G in runqueue, so P has to get some G from other P to execute. Generally speaking, if P wants to get a task from other P, it usually takes run half of the queue, which ensures that each OS thread can be fully used, as shown below:
1. The number of P:
2. Number of M:
M has no absolute relationship with the number of P. If one M blocks, P will create or switch to another M. Therefore, even if the default number of P is 1, many M may be created. come out.
3. When P is created: After determining the maximum number n of P, the runtime system will create n Ps based on this number.
4. When M is created: There is not enough M to associate with P and run the runnable G in it. For example, if all M are blocked at this time, and there are still many ready tasks in P, it will look for an idle M. If there is no idle M, a new M will be created.
When M is blocked due to a system call (G running on M enters a system call), M and P will be separated. If there are tasks in P's ready queue at this time,
P It will associate with an idle M, or create an M for association. (That is to say, go does not handle IO blocking like libtask? Not sure.)
If all tasks in a P's ready queue have been executed, then P will try to take it out from other P's ready queues Part of it is put into its own ready queue to ensure that each P's ready queue has tasks to execute.
The above is the detailed content of About Golang coroutine scheduling. For more information, please follow other related articles on the PHP Chinese website!