Principle of GMP
In the new scheduler, M (Thread) and G (goroutine) are listed, and P (Processor) is introduced.
Processor, which contains the resources to run the Goroutine. If a thread wants to run the Goroutine, it must first fetch P, which also contains a runnable G queue. (1) model of the GMP
In Go, the thread is the entity that runs the Goroutine, and the scheduler’s function is to assign runnable Goroutines to worker threads.
Global Queue: Stores G waiting to run. The local queue of P: similar to the global queue, stores G waiting to run. The number of stores is limited, not more than 256. When G 'is created, G' is preferentially added to the local queue of P. If the queue is full, half of G in the local queue will be moved to the global queue. P list: All PS are created when the program starts and stored in an array, up to GOMAXPROCS(configurable). M: A thread that wants to run a task has to get P from the local queue of P to get G. When the queue of P is empty, M will also try to get some G from the global queue and put it into the local queue of P, or steal half from the local queue of other P and put it into its own local queue of P. M runs G, and after G executes, M gets the next G from P, and so on.Copy the code
The Goroutine scheduler and OS scheduler are combined by M, each of which represents one kernel thread, and the OS scheduler is responsible for allocating the kernel thread to the CPU core for execution.
The number of P's and M'sCopy the code
1. Quantity of P:
It is determined by the startup environment variable $GOMAXPROCS or by the Runtime method GOMAXPROCS(). This means that only $GOMAXPROCS of goroutines are running at any point in the program execution.Copy the code
2. Quantity of M:
Limitations of the GO language itself: When the GO program starts, it sets the maximum number of M, which is 10000 by default. However, the kernel can hardly support such a large number of threads, so this limit can be ignored. Runtime /debug SetMaxThreads sets the maximum number of threads in M. If an M is blocked, a new M will be created.Copy the code
There is no absolute relationship between M and the number of P. If one M blocks, P will create or switch another M. Therefore, even if the default number of P is 1, it is possible to create many M.
When P and M will be createdCopy the code
1. When P is created: After determining the maximum number of P, n, the runtime system creates n P based on this number.
2. When M is created: there are not enough M to associate P with and run the runnable G in it. For example, if all M is blocked, and there are many ready tasks in P, it will look for free M, and there are no free, it will create a new M. (2) Design strategy of scheduler
Reuse threads: Avoid frequent creation and destruction of threads, but reuse threads.
1) Work Stealing
When this thread has no running G, try to steal G from another thread’s bound P instead of destroying the thread.
2) Hand off mechanism
When this thread blocks on a system call due to G, the thread releases the bound P and transfers the P to another idle thread.
Take advantage of parallelism: GOMAXPROCS sets the number of P’s to a maximum of GOMAXPROCS threads running on multiple cpus at the same time. GOMAXPROCS also limits the amount of concurrency. For example, if GOMAXPROCS = number of cores /2, at most half of the CPU cores are used for parallelism.
Preempt: In a Coroutine, it is necessary to wait for a coroutine to voluntarily surrender the CPU before the next coroutine is executed. In Go, a goroutine can occupy up to 10ms of CPU to prevent other Goroutines from starving to death. This is one of the things that makes Goroutine different from Coroutine.
Global G-queues: In the new scheduler, the global G-queue still exists, but it has been disabled. When M cannot steal G from any other P, it can steal G from the global G-queue. (3) Go func () scheduling process
From the above figure, we can draw several conclusions:
1. We create a Goroutine by going func ();
There are two queues that store G, a local queue for the local scheduler P and a global QUEUE for G. The newly created G is stored in the local queue of P and in the global queue if the local queue of P is full.
3, G can only run in M, a M must hold a P, M and P is 1:1 relationship. M will eject an executable G from P’s local queue to execute. If P’s local queue is empty, M will steal an executable G from other MP combinations to execute.
4, a process of M scheduling G execution is a cyclic mechanism;
5. M will block if a syscall or other blocking operation occurs while M is executing a G, and the Runtime will detach M from P if some G is currently executing. A new operating system thread is then created (and reused if free threads are available) to serve the P;
6. When the M system call ends, G attempts to fetch a free execution of P and place it on the local queue of P. If P is not available, the thread M becomes dormant, joins the idle thread, and the G is placed in the global queue. (4) The life cycle of the scheduler
Special M0 and G0
M0
M0 is the main thread numbered 0 after starting the program. The instance of this M is allocated on the global runtime. M0 does not need to be allocated on the heap. M0 performs initialization and starts the first G, after which M0 is the same as any other M.
G0
G0 is the first gourtine that is created every time an M is started. G0 is used only by the G responsible for scheduling. G0 does not point to any executable function, and each M has its own G0. The stack space of G0 is used during scheduling or system calls, and the G0 of global variables is the G0 of M0.