5
WeChat search [ brain into fried fish ] Follow this fried fish with liver-fried liver. This article GitHub github.com/eddycjy/blog has been included, there are my series of articles, materials and open source Go books.

Hello everyone, I am fried fish.

Recently, it is the season of interviews. In my Go reader exchange group, many friends appeared in some Go interview questions that they encountered during the interview process.

Today’s protagonist is the extended question (question) of the GMP model of the universal question of the Go interview, that is, " GMP model, why is there a P ?"

Behind the further consideration of the question, in fact, the essence of this question is to ask: "160c043d29b009 GMP model, why not directly bind G and M and it is finished, but one more P is needed, so troublesome, why is it to solve? What's the problem ?"

This article will take you to explore the reasons for the changes in GM and GMP models.

GM model

Before Go1.1, Go's scheduling model was actually a GM model, that is, there was no P.

Today I will take everyone to review the past design.

Decrypt the Go1.0 source code

One of the ways we understand something is to look at the source code and take a look at the core key steps of scheduler source code

static void
schedule(G *gp)
{
    ...
    schedlock();
    if(gp != nil) {
        ...
        switch(gp->status){
        case Grunnable:
        case Gdead:
            // Shouldn't have been running!
            runtime·throw("bad gp->status in sched");
        case Grunning:
            gp->status = Grunnable;
            gput(gp);
            break;
        }

    gp = nextgandunlock();
    gp->readyonstop = 0;
    gp->status = Grunning;
    m->curg = gp;
    gp->m = m;
    ...
    runtime·gogo(&gp->sched, 0);
}
  • Call the schedlock method to obtain the global lock.
  • After successfully acquiring the global lock, change the current Goroutine state from Running (being scheduled) state to Runnable (can be scheduled) state.
  • Call the gput method to save the current Goroutine running status and other information for subsequent use;
  • Call the nextgandunlock method to find the next runnable Goroutine, and release the global lock for other schedulers.
  • After getting the next Goroutine to be run, change its running status to Running.
  • Call the runtime·gogo method to run the next Goroutine obtained just now to be executed.

Thinking about the GM model

Through the analysis of the scheduler source code of Go1.0.1, we can find an interesting point. That is the scheduler itself (schedule method), in the normal process, it will not return, that is, it will not end the main process.

G-M模型简图

He will continue to run the scheduling process. After GoroutineA is completed, he will start looking for GoroutineB. Once he finds B, he will hand over the scheduling rights of A that has been completed to B, so that GoroutineB will start to be scheduled, that is, run.

Of course, there are also Gs that are being blocked (Blocked). Assuming that G is doing some system and network calls, it will cause G to stagnate. At this time, M (system thread) will be put in the kernel queue again, waiting for a new round of wake-up.

Disadvantages of the GM model

On the surface, the GM model seems unbreakable and flawless. But why change it?

In 2012, Dmitry Vyukov published the article " Scalable Go Scheduler Design Doc ", which is still the main target of major research articles on Go scheduler. The article describes the overall reasons and considerations. The following content will Quote the article.

The current Goroutine scheduler (referring to the GM model of Go1.0) limits the scalability of concurrent programs written in Go, especially high-throughput servers and parallel computing programs.

The implementation has the following problems:

  • There is a single global mutex (Sched.Lock) and centralized state management:

    • Mutex needs to protect all goroutine-related operations (creation, completion, rearrangement, etc.), resulting in serious lock competition.
  • Questions passed by Goroutine:

    • Goroutine (G) transfer (G.nextg): Worker threads (M's) will often transfer runnable goroutines.
    • The above may lead to increased delay and additional overhead. Each M must be able to execute any runnable G, especially the M that just created G.
  • Each M needs to be a memory cache (M.mcache):

    • It will cause excessive resource consumption (each mcache can absorb 2M memory cache and other caches), and the data locality is poor.
  • Frequent thread blocking/unblocking:

    • In the presence of syscalls, threads are often blocked and unblocked. This adds a lot of extra performance overhead.

GMP model

In order to solve the above problems of the GM model, in Go1.1, Dmitry Vyukov added a P (Processor) component on the basis of the GM model. And implement the Work Stealing algorithm to solve some new problems.

GMP model, in the previous article "Go Group Friends Question: How much is the number of Goroutines controlled, will it affect GC and scheduling? It has already been explained in ".

Friends who feel good can pay attention to it, and I won't repeat it here.

What change it brings

What changes will it bring after adding P? Let's talk more explicitly.

  • Each P has its own local queue, which greatly reduces the direct dependence on the global queue. The result is a reduction in lock contention. The main performance overhead of the GM model is lock contention.
  • In terms of the relative balance of each P, the Work Stealing algorithm is also implemented in the GMP model. If P's local queue is empty, runnable G will be stolen from the global queue or other P's local queues to run, reducing idling. Improved resource utilization.

Why should there be P

At this time, some small partners will be puzzled. If you want to implement local queues and Work Stealing algorithms, why not add them directly to M. M can still implement similar components. Why add another P component?

Combined with the positioning of M (system thread), if you do this, there are the following problems:

  • Generally speaking, the number of M will be more than P. Like in Go, the default number of M is 10000, and the default number of P is the number of CPU cores. In addition, due to the properties of M, that is, if there is a system blocking call, and M is blocked, and it is not enough, M will continue to increase.
  • If M keeps increasing, if the local queue is mounted on M, it means that the local queue will also increase. This is obviously unreasonable, because the management of local queues will become complicated, and the performance of Work Stealing will drop drastically.
  • After M is blocked by a system call, we expect to assign his unexecuted tasks to other tasks to continue running, rather than stop them all as soon as they are blocked.

Therefore, it is unreasonable to use M, so introducing a new component P and associating the local queue to P can solve this problem well.

to sum up

Today's article combines some historical conditions, cause analysis, and solution descriptions of the entire Go language scheduler.

"GMP model, why there is a P?" This question is like a system design understanding, because many people now hardly memorize the GMP model or go through instant noodles in order to deal with interviews. And to understand the real reason behind it is what we have to learn and understand.

Only by knowing it and knowing why can we break the game.

If you have any questions please comment and feedback exchange area, best relationship is mutual achievement , everybody thumbs is fried fish maximum power of creation, thanks for the support.

Article continuously updated, can be found [micro letter head into the fried fish] read, reply [ 000 ] have an interview algorithm solution to a problem-tier manufacturers and materials prepared for me; this article GitHub github.com/eddycjy/blog been included , Welcome Star to urge you to update.

煎鱼
8.4k 声望12.8k 粉丝