Thread implementation model

  • User-level threading model
  • Kernel-level threading model
  • Two-level threading model


Go thread model core element MPG

Go builds a unique two-level threading model on top of the kernel threads provided by the operating system.

A G execution requires P and M support. Once P and M are associated, a valid G environment (kernel thread + context) is formed.

Each P contains a queue of runnable G, which in turn is passed to the current M associated with the local P, and the run-time is obtained.

  • M
  1. Machine abbreviation, a M represents a user-space kernel thread, and kernel thread (KSE) is always one-to-one correspondence, in the life cycle of M, will and only a KSE will be associated.
  2. As soon as M is created, it is added to the global M list.
  3. Sometimes M is stopped (when G cannot be found to run, etc.) and M is added to the scheduler’s free M list, from which the scheduler first tries to fetch an unused M when it needs one.
  4. The maximum value of M used by a single Go program can be set, starting at 10000
  5. M start and stop:

When M obstruction due to a system call (more specifically, is he running G into the system call), the runtime system take M and associated P separation, if the P can run queue and G is not running, then the runtime system will go to the scheduler free M list to find a free M, if you can’t find new, then the P, And make the rest of these G’s run.

  • p
  1. Short for Processor, a P for the resource required to execute a Go snippet (context)
  2. The scheduler will timely establish or disassociate P with different M, so that the runnable G in P can obtain the running time in time.
  3. By default, the number of P is equal to the current number of CPU cores. You can use runtime.GOMAXPROCS to change the number of P. The number of P is the number of runnable G queues.
  4. When a P is no longer associated with any M, and its runnable G queue is empty, it is put into the scheduler’s free P list, from which it is fetched when a free P association M is needed.
  5. The P contains a free list of G’s that have already been run. When it reaches a certain point, the runtime system moves some of the G’s into the scheduler’s free G list.
  6. The state of P:
- Pidel: the initialization is complete, p is not associated with m, and is put into the free P list of the scheduler. - Prunning: P is associated with M, and M is executing G. -psyscall: g running in p is being called by the system. -pgcstop: scheduler stops scheduling. Runtime is GC(Runtime tries to GC all P's in the global P list in this state); There are serial run-time tasks waiting to execute --> stop current M--> release local P--> P is pgcstop-pdead: current P is no longer used (the excess P is in this state when the runtime.GOMAXPROCS is called to reduce the number of P)Copy the code
  • Any p in a non-PDEAD state will be placed in Pgcstop when the system wants to stop. When scheduling is enabled, it will not return to its original state but stay in Pidle (on the same starting line).
  • Any P that is not Pgcstop may be considered redundant due to the shrinking of the global P-list and placed in the Pdead state, its runnable G queue will be moved to the scheduler’s runnable G queue, and the G in its free G list will be moved to the scheduler’s free G queue.

  • G
  1. Short for Goroutine, a G stands for a go snippet, G is an encapsulation of go code. The Go compiler turns the Go statement into a call to the internal function newproc, passing the Go function and its arguments as arguments.
  2. The new G is added to the global G list first, and, after initialization, is added to the runnext field of local P, where the user stores the freshly made G so it can run it earlier. If runNext already has a G, the existing G is kicked to the end of the runnable G queue for that P. If the queue is full, the G can only be appended to the scheduler’s runnable G queue.
  3. When the go statement wants to enable a G, the runtime system first attempts to wrap the go function with a ready-made G from the corresponding free G list of P. It is only possible to create a new G if G is not available.
  4. When the runtime system finds that the local P-free G is too low, it tries to transfer some from the scheduler’s free G list. If the free G queue of local P is found to be full, some is transferred to the free G list of the scheduler.
  5. All free G-lists are first in last out.
  6. After a G is enabled, it is appended to a runnable G queue of P to wait for the runnable G queue.
  7. State of G:
-gidle (just allocated, not initialized) -grunnable (after initialized, usually in p local runnable queue waiting to run; -grunning (running) -gsyscall (running system call, out of system call state and running) -Grunning (running) -gsyscall (running system call, out of system call state and running) ** Runnable G queue placed in ** scheduler **; -gwaiting (blocked by channel, IO operation, Timer, time.Sleep, etc.). After the event comes, G will wake up. -grunnable is entered, waiting to be called, some are put into the runnable G queue of the ** local P, some are put into the runnable G queue of the ** scheduler, and some are run directly (e.g., just done network I/O). -gdead (idle, put into ** local P or scheduler free G queue **) -gcopystack (indicates that G stack is being moved, why is it moved? Because of stack expansion or shrinkage) - Gscan(this state cannot exist alone, and is combined with other states to form a new state, which occurs during GC scan)Copy the code

Container element

Unlabeled lists are one-way lists

scope M the list A list of P G queue/list
Run-time system Global M-list Global P-list (an array) Global G-list (a slice)
The scheduler Free M list Free P list Runnable G queue, free G list (2)
Local p Runnable G queue, free G list (1)

Round scheduling of the scheduler

In the two-level threading model, some of the scheduling tasks are performed by programs outside the operating system kernel. In Go, the scheduler is responsible for this part of the scheduling tasks, and the object of scheduling is MPG. Round scheduling is the core flow of the Go scheduler. It is triggered in many cases. After a series of initializations at the start of the user program, round scheduling starts for the first time and causes the G that encapsulates Main to run. The block, end, and exit system calls of a particular G run are scheduled in a round.

  1. The scheduler determines whether the current M is locked, if so, it immediately stops scheduling and stops the current M, and does not look for a runnable G for the current M. When G locked to it is runnable, M wakes up and continues executing G.
  2. If not locked with task G, check if any run-time serial tasks (which require stopping the Go scheduler) are waiting to execute, stop the current M-wait for the run-time serial tasks to complete. As soon as the serial task is completed, the M will wake up for another round of scheduling.
  3. There is no lock, no run-time serial task, start to look for G, once found G, judge whether G is locked with other M, if so, it will run G with its locked M; If not, just run G.

To find the G

The first stage

Find the runnable G: runtime.findrunnable function that returns a Grunnable G. The process is divided into 2 stages and 10 steps

  • Step1:

A runnable G queue finalizer that gets the execution finalizer G and puts it into local P is a finalizer that its associated object executes before being collected by the garbage collector. The execution of this function is handled by a special G. The scheduler will determine that the G has completed its task, retrieve it, place it in a Grunable state, and place it in the runnable G queue of local P.

  • Step2:

Retrieves G from the executable G queue of local P and returns the G

  • Step3:

Retrieves G from the scheduler’s executable G queue and returns the G

  • Step4:

The scheduler gets a list of G’s from the Netpoller, returns the header G, and puts the rest of the G’s into the scheduler’s runnable G queue. When a G tries to read or write on a network connection, the underlying program prepares for it and puts the G into a Gwaiting state. Once ready, the underlying program returns the corresponding event, which causes The Netpoller to immediately notify the WAITING G. Getting G from Netpoller means getting G’s that have already received notifications, putting them into Grunnable state and waiting to run. This type of G is not put into some runnable G queue of P

  • Step5:

Get G from the runnable G queue of other P, return G selects P from the global P list using a pseudo-random algorithm, and then steals half of G from its runnable G queue to the runnable G queue of local P. And return the stolen first G as the result. The process of picking P and stealing G is repeated many times. Prerequisite: There is a non-idle P in addition to the local P

End of Phase 1

The second stage

  • Step6:

If the system is in the GC tag phase and local P is available for GC tag tasks, the GC tag dedicated G held by local P is set to Grunnable and the G is returned

  • Step7:

Retrieve G from the scheduler’s runnable G queue and return the G (why search again? Step3 and step4 have been lost. Is there any circumstance after 3 and 4 that G will be added to the runnable G queue of the scheduler?) If G still cannot be found to run, the local P is disassociated from the current M and P is placed in the scheduler’s free P list.

  • Step8:

Get G from the runnable G queue of each P in the global P list, return the G to traverse the global P list, as long as the runnable G queue of a P is not found to be empty, take a P from the free P list of the scheduler, and associated with the current M, and then return to the first stage to search G.

  • Step9:

Get GC marking task G, association holds GC marking special G P to the current M if the system is in the midst of the GC mark phase, the tasks related to the GC mark of global resources available, the scheduler from its free list out a P, P if the P have a GC mark special G, associated with the P and the current M, then the third support in the second stage

  • Step10:

Fetch G from network I/O(netpoller) and return to fetch a list of G from netpoller. Unlike step4, netpoller has been initialized and I/O has been performed, blocking until G is available. However, if the Netpoller has not been initialized, or no I/O operations have occurred, this step will be skipped.

Simple solutions of nouns

  • KSE: Kernel scheduling entity, an object that can be scheduled by the kernel’s scheduler, also known as kernel-level thread, is the operating system kernel minimum scheduling unit.

  • System call: a standard set of interfaces provided by the operating system kernel to user-space programs. Through this interface, the user – mode program can access hardware devices, so as to apply for system resources, read and write devices, create new processes and other operations