<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Chapter 9 The goroutine Scheduler on Go: Under the Hood</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/</link><description>Recent content in Chapter 9 The goroutine Scheduler on Go: Under the Hood</description><generator>Hugo</generator><language>en</language><atom:link href="https://golang.design/under-the-hood/en/part3concurrency/ch09sched/index.xml" rel="self" type="application/rss+xml"/><item><title>9.1 The Scheduling Problem and the GMP Model</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/model/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/model/</guid><description>&lt;h1 id="91-the-scheduling-problem-and-the-gmp-model"&gt;9.1 The Scheduling Problem and the GMP Model&lt;/h1&gt;
&lt;p&gt;Write &lt;code&gt;go f()&lt;/code&gt; and a goroutine starts running. Behind that one line sits the most intricate
machine in the Go runtime: the scheduler. It has to answer a question that is not at all
simple: how can tens of thousands of goroutines take turns on a small handful of CPU cores,
running fast while letting the user barely notice it is there. This section first lays out the
problem the scheduler must solve, its overall skeleton, and its place in the larger family of
concurrent runtimes. Later sections then go deep into each component.&lt;/p&gt;</description></item><item><title>9.2 Work-Stealing Scheduling</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/steal/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/steal/</guid><description>&lt;h1 id="92-work-stealing-scheduling"&gt;9.2 Work-Stealing Scheduling&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././model"&gt;9.1&lt;/a&gt; left us a question: each P has its own local queue, so work is bound to be distributed unevenly. Some Ps are overwhelmed while others sit idle. How to spread the load without introducing a central bottleneck is the core difficulty of concurrent scheduling. Go&amp;rsquo;s answer is a design with thirty years of theory behind it, one that recurs throughout the industry: work stealing.&lt;/p&gt;
&lt;p&gt;This section goes a bit deeper than the rest. We first make clear what Go does, then trace back to the scheduling theory behind it (why it is &amp;ldquo;provably good&amp;rdquo;), then look across its different incarnations in systems such as Cilk, Java, and Rust, and finally stop at the questions that remain open.&lt;/p&gt;</description></item><item><title>9.3 The MPG Model and the Units of Concurrent Scheduling</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/mpg/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/mpg/</guid><description>&lt;h1 id="93-the-mpg-model-and-the-units-of-concurrent-scheduling"&gt;9.3 The MPG Model and the Units of Concurrent Scheduling&lt;/h1&gt;
&lt;p&gt;The first question a scheduler must answer is not &amp;ldquo;how to schedule&amp;rdquo; but &amp;ldquo;what to schedule.&amp;rdquo; Go calls the thing being scheduled a goroutine, and carries it on a triple of M, P, and G. Before we get our hands on the scheduling algorithm (from &lt;a href=".././schedule"&gt;9.4&lt;/a&gt; onward), this section settles these three scheduling units: what a goroutine really is within the lineage of computer science, how its running context is encoded, why scheduling itself has to happen on a special g0, what states a goroutine passes through over its life, and how the worker thread M that carries it is parked and unparked. Once these few things are clear, the scheduling algorithms that follow are just &amp;ldquo;moving G around among these units.&amp;rdquo;&lt;/p&gt;</description></item><item><title>9.4 The Scheduling Loop</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/schedule/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/schedule/</guid><description>&lt;h1 id="94-the-scheduling-loop"&gt;9.4 The Scheduling Loop&lt;/h1&gt;
&lt;p&gt;The previous sections laid out the materials: we know what G, M, and P are (&lt;a href=".././mpg"&gt;9.3&lt;/a&gt;), and we know how an M finds work (&lt;a href=".././steal"&gt;9.2&lt;/a&gt;). This section actually sets them spinning, watching how the scheduling loop ceaselessly picks and runs goroutines on a single thread, and how it strikes a balance between &amp;ldquo;letting a single goroutine run a little longer&amp;rdquo; (throughput and locality) and &amp;ldquo;not letting any goroutine starve&amp;rdquo; (fairness).&lt;/p&gt;</description></item><item><title>9.5 Thread Management</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/thread/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/thread/</guid><description>&lt;h1 id="95-thread-management"&gt;9.5 Thread Management&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././model"&gt;9.1&lt;/a&gt; laid down the three-layer GMP structure: G is the user-level unit of execution, P is the scheduling permit and the carrier of local resources, and M is the leg actually borrowed from the operating system. The previous sections dwelt mostly on G and P; this section turns its attention to M, and answers several questions we have kept deferring: what M actually is, where it comes from, why GOMAXPROCS caps P while the thread count is often larger, why a single blocking system call does not drag down the other G alongside it, and what price the runtime pays when a user wants to pin a goroutine to a specific thread (&lt;code&gt;LockOSThread&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>9.6 Signal Handling</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/signal/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/signal/</guid><description>&lt;h1 id="96-signal-handling"&gt;9.6 Signal Handling&lt;/h1&gt;
&lt;p&gt;Operating system signals are asynchronous and low-level: a signal may interrupt any thread at any
moment, and what the signal handler is allowed to do is severely constrained. What Go users want,
on the other hand, is usually to wire a channel to &lt;code&gt;SIGINT&lt;/code&gt; with &lt;code&gt;signal.Notify&lt;/code&gt; and shut down
gracefully when it arrives. What the runtime has to do is build a bridge between these two: turn a
treacherous asynchronous signal into an event a goroutine can consume in peace. Every design choice
on this bridge is governed by one hard constraint: what can be done inside a signal context. Once
you understand this constraint, the rest of this section&amp;rsquo;s mechanics are merely its corollaries.&lt;/p&gt;</description></item><item><title>9.7 Cooperation and Preemption</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/preemption/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/preemption/</guid><description>&lt;h1 id="97-cooperation-and-preemption"&gt;9.7 Cooperation and Preemption&lt;/h1&gt;
&lt;p&gt;In &lt;a href=".././schedule"&gt;9.5 The Scheduling Loop&lt;/a&gt; we left an open question: if some G runs for too long,
how can other G&amp;rsquo;s still get scheduled? The answer cannot avoid an old pair of concepts from
scheduling theory, cooperative versus preemptive. Cooperative scheduling relies on the scheduled
party voluntarily yielding; preemptive scheduling relies on the scheduler interrupting the
scheduled party from the outside.&lt;/p&gt;
&lt;p&gt;The Go runtime has nothing like the hardware interrupt capability of an operating system kernel.
The work-stealing scheduler (&lt;a href=".././steal"&gt;9.2&lt;/a&gt;) is essentially first-come-first-served cooperative
scheduling. How it can still forcibly interrupt a G that refuses to yield, without sacrificing this
premise, is the design this section sets out to make clear. The thread starts from a theoretical
question: by what right can the runtime not stop a goroutine at an arbitrary instruction?&lt;/p&gt;</description></item><item><title>9.8 System Monitoring</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/sysmon/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/sysmon/</guid><description>&lt;h1 id="98-system-monitoring"&gt;9.8 System Monitoring&lt;/h1&gt;
&lt;p&gt;The scheduler&amp;rsquo;s ordinary path was laid out in &lt;a href=".././schedule"&gt;9.4 The Scheduling Loop&lt;/a&gt;: one M binds to one P,
takes a Goroutine off the queue, runs it, then takes the next. This path rests on one premise, that it gets a chance to run at all.
Yet once all the Ps are mired in long system calls, or some Goroutine spins forever and holds a P hostage, ordinary scheduling
seizes up. No one takes the P back, and no one polls the network. Put differently, cooperative logic that runs on a P
cannot deal with the situation where &amp;ldquo;the P itself cannot move.&amp;rdquo;&lt;/p&gt;</description></item><item><title>9.9 The Network Poller</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/poller/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/poller/</guid><description>&lt;h1 id="99-the-network-poller"&gt;9.9 The Network Poller&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Source facts verified against &lt;code&gt;src/runtime/netpoll.go&lt;/code&gt; and its per-platform
implementations (&lt;code&gt;netpoll_epoll.go&lt;/code&gt;, &lt;code&gt;netpoll_kqueue.go&lt;/code&gt;, and so on) and
&lt;code&gt;src/internal/poll/fd_unix.go&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Go&amp;rsquo;s network code looks blocking: &lt;code&gt;conn.Read&lt;/code&gt; simply &amp;ldquo;sits&amp;rdquo; there waiting for data.
But if it truly blocked the operating system thread it runs on, then ten thousand
goroutines waiting on the network would tie up ten thousand threads, and the M:N model
that &lt;a href=".././model"&gt;9.1&lt;/a&gt; worked so hard to build would collapse in an instant. What lets
the blocking style still scale is the network poller (netpoller). Behind it lies a long
history about &amp;ldquo;how to tend a vast number of connections with a handful of threads.&amp;rdquo; This
section first lays out that history and the design axes behind it, then looks at how Go
hides a mature event mechanism inside the runtime so that you write synchronous code and
run event-driven I/O.&lt;/p&gt;</description></item><item><title>9.10 Timers</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/timer/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/timer/</guid><description>&lt;h1 id="910-timers"&gt;9.10 Timers&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;time.Sleep&lt;/code&gt;, &lt;code&gt;time.After&lt;/code&gt;, &lt;code&gt;time.Timer&lt;/code&gt;, &lt;code&gt;time.Ticker&lt;/code&gt;, and even &lt;code&gt;SetDeadline&lt;/code&gt; on network reads and writes
all rest on the same timer machinery. It has to answer a question that looks simple but is in fact subtle, a
question about data structures: when thousands of timers exist at once, how do we efficiently know &amp;ldquo;who should
be woken next, and when,&amp;rdquo; without burning a thread to do it. This section starts from that abstract problem,
makes the trade-offs of the various solutions clear, and then lands on Go&amp;rsquo;s choice and how it evolved.&lt;/p&gt;</description></item><item><title>9.11 NUMA Awareness and the Future of the Scheduler</title><link>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/numa/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part3concurrency/ch09sched/numa/</guid><description>&lt;h1 id="911-numa-awareness-and-the-future-of-the-scheduler"&gt;9.11 NUMA Awareness and the Future of the Scheduler&lt;/h1&gt;
&lt;p&gt;The scheduler described in the preceding sections rests on an assumption that was never spelled out: every M reaches memory equally fast, and moving a G between any two Ps costs the same. On a laptop or a single-socket server, that assumption is nearly true. But once you put the program on a large multi-socket server it begins to break, and the more cores there are, the wider the gap. This section is about that crack: where it comes from, why the Go scheduler has looked past it for so long, a NUMA-aware design that was carefully thought through yet never shipped, and how users today work around it.&lt;/p&gt;</description></item></channel></rss>