In Go's goroutine scheduler, what do G, M and P stand for, and what does each represent?
answer
- three letters, three different objects
- one belongs to the kernel, two do not
- a thread needs a permit to run Go
- the permit count is GOMAXPROCS
basics
~20 sG is a goroutine, M is an OS thread, and P is a scheduling context an M must hold to run Go code. GOMAXPROCS sets how many Ps exist, so it caps how much Go code runs at once.
solid answer
~50 sG, M and P are the three runtime structures Go's scheduler juggles. A **G** is a goroutine: its stack, its instruction pointer and its scheduling state. An **M** is a machine, meaning a real OS thread — the only one of the three the kernel knows about. A **P** is a processor, a scheduling context that carries the resources needed to run Go code: a run queue of ready goroutines and a per-P memory cache. Execution is always a triple: an M picks up a P, the P hands it a G, and the M runs that G's code. There are usually far more Gs than Ms, and the number of Ps is fixed by `GOMAXPROCS`, so `GOMAXPROCS` is the ceiling on how many goroutines execute Go code simultaneously — that is Go's M:N multiplexing.
go deeper
Be ready to name all three letters without hesitating and say which one the operating system knows about. Saying "P is a CPU core" is the answer that ends this question badly.
An interviewer expects you to describe execution as an M+P+G triple and to explain that goroutine switching happens in user space because of it. Know that the P count comes from GOMAXPROCS.
Show that you can read runtime output through this vocabulary — one stack per G in a dump, Ms in a host thread count, Ps in scheduler output — and that you know the three counts move independently in production.
Own the framing that the P count is a capacity decision your platform makes on every service, not a detail: it is the number of Go-code execution slots each container gets, and it defaults from the environment you hand the process.
## The three structures Go's scheduler is built from three runtime types, named by single letters in the runtime source and in every conference talk about it. **G — goroutine.** A `g` is the runtime's record of one goroutine: its stack (small at first, a couple of kilobytes, and grown by the runtime when needed), the saved registers and program counter it resumes from, its status (runnable, running, waiting, dead), and bookkeeping such as which M is currently running it. A `go f()` statement allocates or reuses a `g` and marks it runnable. It does **not** ask the operating system for anything — this is why a program can hold hundreds of thousands of goroutines, where the same number of OS threads would be impossible. **M — machine.** An `m` is an actual OS thread. This is the only one of the three the kernel schedules, sees in a thread listing, or delivers a signal to. An M executes machine code; on its own it has no idea which goroutine should run next. **P — processor.** A `p` is a *scheduling context*, and it is the piece people find surprising because it corresponds to nothing in the operating system. A P is a permit plus a working set: it owns a queue of goroutines that are ready to run, and it owns per-P caches (most notably a memory-allocation cache) so that common operations need no global lock. **An M must be holding a P to execute Go code.** The number of Ps is created at startup from `GOMAXPROCS` and does not drift on its own. ## Why the triple Running code always means a triple: **M + P + G**. The thread supplies execution, the P supplies the right to run Go code and the state to decide what runs, and the G supplies the actual work. When a goroutine stops being runnable — it blocks on a channel, sleeps, or finishes — the M does not stop; it takes the next G from the P it holds and continues. Goroutine switching therefore happens entirely in user space, with no kernel transition, which is what makes it cheap. The P also explains a boundary that confuses newcomers. If parallelism were bounded by the *thread count*, then every thread that stopped being useful for Go code would eat a slot in that budget. Separating the permit (P) from the worker (M) lets the runtime keep exactly `GOMAXPROCS` Go-code slots while the thread count moves independently. ## M:N multiplexing Go is an M:N runtime: many goroutines are multiplexed onto a smaller number of OS threads. Concretely: - **Gs** — as many as your program creates; thousands or millions is normal. - **Ms** — as many as the runtime finds it needs; not fixed by you and not equal to the number of Ps. - **Ps** — exactly `GOMAXPROCS`, and that number is what bounds simultaneous execution of Go code. So "how many goroutines can run at the same time?" has two answers depending on what you mean. *Concurrently* — in flight, interleaved — as many as you create. *Simultaneously*, on different CPUs at the same instant — at most the number of Ps. ## Where the letters show up Once you know the vocabulary, the runtime's own output becomes readable: scheduler debug output talks about Ps and their queues, a panic dump prints one stack per G, and thread-level tooling on the host counts Ms. `runtime.GOMAXPROCS(0)` returns the current number of Ps without changing it, and `runtime.NumCPU()` returns the logical CPUs the process may use — printing both at startup is the cheapest orientation you can get. ## The misconceptions to avoid - **A P is not a CPU core.** It is a runtime bookkeeping object. Its count often *defaults* from the available CPU count, which is why the two are so easily conflated. - **A goroutine is not a thread.** Creating one does not create an M. - **`GOMAXPROCS` is not a goroutine limit** and not a thread limit; it is the P count. - **The kernel never sees a goroutine.** It schedules Ms; the Go runtime schedules Gs onto them.
- Which of the three does the operating system actually schedule?Only the M. Ms are real OS threads, so the kernel schedules them, delivers signals to them and shows them in thread listings. Gs and Ps are pure Go runtime structures with no kernel counterpart, which is why a host-level thread tool can never show you goroutines, and why switching between goroutines costs no system call.
- Does `go f()` create an OS thread?No. It allocates or reuses a `g`, sets up its stack and entry point, and marks it runnable so some P will hand it to some M later. Threads are created by the runtime on its own schedule, independently of how many goroutines you start — that is exactly why goroutines are cheap enough to create per request.
- What decides how many Ps a program has?`GOMAXPROCS`. The runtime derives a default at startup from the CPUs the process may use, and on recent Go on Linux also from the container's CPU limit; you can override it with the `GOMAXPROCS` environment variable or by calling `runtime.GOMAXPROCS(n)`. `runtime.GOMAXPROCS(0)` reads the current value without changing it.
saying these in an interview costs you the question
- Says P is a physical CPU core
- Says each goroutine gets its own OS thread
- Thinks GOMAXPROCS caps how many goroutines you may create
- Swaps the letters: calls M the goroutine
- Claims the kernel schedules goroutines directly