Before you know the scheduler of go, you need to understand why you need it, because we might think that the OS kernel is not already a thread scheduler?
Everyone familiar with the POSIX API knows that the POSIX scenario is largely a logical description and extension of the UNIX process approach model, with many similarities. Thread has its own signal mask, CPU affinity and so on. But many features are cumbersome for go programs. This is especially the time-consuming context switch. Another reason is that the go garbage collection requires all the goroutine to stop, so there is a consistent state within. The point of time for garbage collection is uncertain, and if you rely on the OS's own scheduler to dispatch, there will be a lot of threads that need to stop working.
A go scheduler can be developed separately, knowing when the memory state is consistent, that is, when the garbage collection starts, the runtime simply waits for the thread that is running on the CPU core, rather than waiting for all the threads.
Mappings between user-space threads and kernel-space threads are: N:1, 1:1, and M:n
N:1 is that multiple (N) user threads are always running on a kernel thread, and context switches are really fast, but they can't really take advantage of multicore.
1:1 means that a user thread runs on only one kernel thread, which can take advantage of multicore, but the context switch is slow.
M:n is that multiple goroutine run on multiple kernel threads, which seems to be able to combine the advantages of both, but undoubtedly increases the difficulty of scheduling.
<img src= "Https://pic2.zhimg.com/50/2f5c6ef32827fb4fc63c60f4f5314610_hd.jpg" Data-rawwidth= "391″data-rawheight=" 103″class= "Content_image" width= "391″>
There are three important structures inside the GO scheduler: m,p,g
M: Represents a real kernel OS thread, like the thread in POSIX, the person who really works
G: Represents a goroutine, it has its own stack, instruction pointer and other information (waiting for the channel, etc.) for dispatch.
P: Represents the context of a dispatch, which can be seen as a local scheduler that allows go code to run on a thread, which is the key to implementing mapping from N:1 to n:m.
<img src= "Https://pic3.zhimg.com/50/67f09d490f69eec14c1824d939938e14_hd.jpg" Data-rawwidth= "400″data-rawheight=" 391″class= "Content_image" width= "400″>
In the figure, there are 2 physical threads m, each of which has a context (P), and each has a running goroutine.
The number of P can be passed through the runtime. Gomaxprocs () to set, it actually represents the true concurrency, that is, how many goroutine can run at the same time.
The gray goroutine in the figure are not running, but are ready for readiness and are waiting to be dispatched. P maintains this queue (called runqueue),
In the go language, it's easy to start a goroutine: Go function is OK, so every go statement is executed, and the Runqueue queue adds a
Goroutine, at the next dispatch point, is removed from the runqueue (how to decide which goroutine to take?) ) a goroutine executes.
Why should I maintain multiple context p? Because when an OS thread is blocked, p can turn to another OS thread!
As seen in the figure, when an OS thread M0 into a block, p runs on the OS thread M1 instead . The scheduler guarantees that there are enough threads to run all of the context P.
<img src= "Https://pic1.zhimg.com/50/f1125f3027ebb2bd5183cf8c9ce4b3f2_hd.jpg" Data-rawwidth= "550″data-rawheight=" 400″class= "Origin_image zh-lightbox-thumb" width= "550″data-original=" https:// Pic1.zhimg.com/f1125f3027ebb2bd5183cf8c9ce4b3f2_r.jpg ">
The M1 in the diagram may be created, or removed from the thread cache. When Mo returns, it must try to get a context p to run Goroutine, which, in general, will steal a context from the other OS threads, steal.
If it's not stolen, it puts Goroutine in a global runqueue and then sleeps (into the thread cache). Contexts also periodically check global runqueue, otherwise the goroutine on global runqueue can never be executed.
<img src= "Https://pic3.zhimg.com/50/31f04bb69d72b72777568063742741cd_hd.jpg" Data-rawwidth= "550″data-rawheight=" 400″class= "Origin_image zh-lightbox-thumb" width= "550″data-original=" https:// Pic3.zhimg.com/31f04bb69d72b72777568063742741cd_r.jpg ">
In another case, the task G assigned by P is quickly executed (uneven distribution), which leads to a context p is idle and the system is busy. But if global Runqueue has no task G, then P will have to take some g from the other context p to execute. In general, if context P steals a task from other context p, it is generally ' stealing ' half of the runqueue, which ensures that every OS thread is fully used.
Reference Link: Https://www.zhihu.com/question/20862617http://morsmachine.dk/go-scheduler