goroutine scheduling mechanism of golang

goroutine scheduling mechanism of golang

I have always been curious about the scheduling mechanism of goroutines. Recently, I was watching the golang source code analysis of Rain Marks (based on go1.4)

Feeling suddenly enlightened and benefited a lot;

To simplify, add some of my own understanding, and sort it out

~~

scheduler

Mainly based on three basic objects, G, M, P (defined in the src/runtime/runtime.h file of the source code)

1. G represents a goroutine object, and every time go is called, a G object will be created

2. M represents a thread, and every time an M is created, an underlying thread will be created; all G tasks are ultimately executed on M

3. P represents a processor, and each running M must be bound to a P, just like a thread must execute on a CPU core

The number of P is GOMAXPROCS (maximum 256), which is fixed at startup and generally not modified; the number of M is not necessarily the same as the number of P (there will be sleeping M or not too many M) (maximum 10000 ); each P holds a local G task queue and also a global G task queue;

As shown below

The global G task queue will be exchanged with each local G task queue according to a certain strategy (if it is full, half of the local queue will be sent to the global queue)

P is stored in a global array (255) and maintains a global P free list

Every time go is called, it will:

1. Create a G object and add it to the local queue or global queue

2. If there is still a free P, create an M

3. M will start an underlying thread and loop through the G tasks it can find

4. The execution order of G tasks is to first look for them from the local queue, and if they do not exist locally, look for them from the global queue (one-time transfer (global G number/P number), and then go to other Ps (one-time transfer half) ,

5. The above G tasks are executed in queue order (that is, the order of go calls). (Does this place feel weird??)

For steps 2-3 above, create an M whose procedure:

1. First find an idle P, if not, return directly, (haha, this place ensures that the process will not occupy more than the number of CPUs you set)

2. Call the system api to create a thread. Different operating systems have different calls. In fact, it is the same as the C language creation process. (windows uses CreateThread, linux uses the clone system call), (*^__^* )Whee……

3. Then the created thread is the real thing, and the G task is executed in a loop

Then there will be a problem. If a system call or G task is executed for too long, it will always occupy this thread. Since the G tasks in the local queue are executed sequentially, other G tasks will be blocked. How to stop the long task Woolen cloth? (I have been looking for this place for a long time~o(╯□╰)o)

In this way, when starting, a thread sysmon will be specially created for monitoring and management, which is a loop internally:

1. Record the G task count schedtick of all P, (schedtick will be incremented after each G task is executed)

2. If it is checked that schedtick has not been incremented, it means that this P has been executing the same G task. If it exceeds a certain time (10ms), add a mark to the stack information of this G task.

3. Then when the G task is executing, if it encounters a non-inline function call, it will check the flag once, then interrupt itself, add itself to the end of the queue, and execute the next G

4. O(∩_∩)O haha~, if there is no non-inline function (sometimes normal small functions will be optimized into inline functions) calls, it will be miserable, and this G task will be executed all the time. Until it ends on its own; if it's an infinite loop and GOMAXPROCS=1, congratulations, hold on! Test it out, it's true

For a G task, the recovery process after interruption:

1. When interrupting, save the stack information in the register to your own G object

2. When it is your turn to execute again, copy the stack information saved by yourself into the register, so that it will continue to run after the last time. ~\(≧▽≦)/~

 

But there is still another problem, that is, the process of system startup. Yuken didn't explain it too clearly. I have always been puzzled by many questions (how did the first M come from?, how did G find the corresponding P? etc.), this It made me sore for a long time~

However, I am obsessed with myself, and I will add it below. Welcome everyone to correct me

1. When the system starts, the main thread runs first, so the first M should be the main thread (according to the understanding of C language, hehe), here is M1, you can see the previous picture

2. Then the main thread will bind the first P1

3. The main function we wrote is actually executed as a goroutine (what the rain marks said)

4. That is, the first P1 has a G1 task, and then the first M1 executes the G1 task (that is, the main function). When creating this G1, there is no need to create M, because M1 already exists.

5. All the goroutines in this main function are bound to the P1 corresponding to the current M1, O(∩_∩)O haha~

6. Then when the goroutine in main (such as G2) is created, a new M2 will be created. The local task queue of the initial P2 in the new M2 is empty, and some will be taken from P1, haha

7. In this way, the two M1 and M2 each perform their own G tasks, and then reciprocate in turn, and this is complete~~~

 

In summary:

Therefore, goroutines are scheduled in a preemptive manner, and a goroutine executes at most 10ms before switching to the next one.

This is similar to the cpu scheduling of the current mainstream system (sliced ​​by time)

windows:20ms

linux:5ms-800ms


It’s almost here. These are described in more detail in the notes of Rain Marks, but many places are messy and complicated. There are a lot of screenings here to facilitate readers’ understanding.

 

Notice:

1. In Golang, the compiler will also try to inline, copy and compile small functions directly. In order to inline, try to eliminate the dead code that the compiler cannot detect. Use the gobuild -gcflags=-m compilation command to view the inside of the program Linked state, I have to say that golang's compilation toolchain is still very powerful, which is very beneficial to program optimization.

 

If you have any questions, welcome to ask,

update at any time

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325386889&siteId=291194637