<?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>/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="/en/part3concurrency/ch09sched/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>9.1 The Scheduling Problem and the GMP Model</title>
      <link>/en/part3concurrency/ch09sched/model/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/model/</guid>
      <description>&lt;h1 id=&#34;91-the-scheduling-problem-and-the-gmp-model&#34;&gt;9.1 The Scheduling Problem and the GMP Model&lt;/h1&gt;&#xA;&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&#xA;machine in the Go runtime: the scheduler. It has to answer a question that is not at all&#xA;simple: how can tens of thousands of goroutines take turns on a small handful of CPU cores,&#xA;running fast while letting the user barely notice it is there. This section first lays out the&#xA;problem the scheduler must solve, its overall skeleton, and its place in the larger family of&#xA;concurrent runtimes. Later sections then go deep into each component.&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.2 Work-Stealing Scheduling</title>
      <link>/en/part3concurrency/ch09sched/steal/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/steal/</guid>
      <description>&lt;h1 id=&#34;92-work-stealing-scheduling&#34;&gt;9.2 Work-Stealing Scheduling&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././model&#34;&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;&#xA;&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>/en/part3concurrency/ch09sched/mpg/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/mpg/</guid>
      <description>&lt;h1 id=&#34;93-the-mpg-model-and-the-units-of-concurrent-scheduling&#34;&gt;9.3 The MPG Model and the Units of Concurrent Scheduling&lt;/h1&gt;&#xA;&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=&#34;.././schedule&#34;&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>/en/part3concurrency/ch09sched/schedule/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/schedule/</guid>
      <description>&lt;h1 id=&#34;94-the-scheduling-loop&#34;&gt;9.4 The Scheduling Loop&lt;/h1&gt;&#xA;&lt;p&gt;The previous sections laid out the materials: we know what G, M, and P are (&lt;a href=&#34;.././mpg&#34;&gt;9.3&lt;/a&gt;), and we know how an M finds work (&lt;a href=&#34;.././steal&#34;&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>/en/part3concurrency/ch09sched/thread/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/thread/</guid>
      <description>&lt;h1 id=&#34;95-thread-management&#34;&gt;9.5 Thread Management&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././model&#34;&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>/en/part3concurrency/ch09sched/signal/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/signal/</guid>
      <description>&lt;h1 id=&#34;96-signal-handling&#34;&gt;9.6 Signal Handling&lt;/h1&gt;&#xA;&lt;p&gt;Operating system signals are asynchronous and low-level: a signal may interrupt any thread at any&#xA;moment, and what the signal handler is allowed to do is severely constrained. What Go users want,&#xA;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&#xA;gracefully when it arrives. What the runtime has to do is build a bridge between these two: turn a&#xA;treacherous asynchronous signal into an event a goroutine can consume in peace. Every design choice&#xA;on this bridge is governed by one hard constraint: what can be done inside a signal context. Once&#xA;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>/en/part3concurrency/ch09sched/preemption/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/preemption/</guid>
      <description>&lt;h1 id=&#34;97-cooperation-and-preemption&#34;&gt;9.7 Cooperation and Preemption&lt;/h1&gt;&#xA;&lt;p&gt;In &lt;a href=&#34;.././schedule&#34;&gt;9.5 The Scheduling Loop&lt;/a&gt; we left an open question: if some G runs for too long,&#xA;how can other G&amp;rsquo;s still get scheduled? The answer cannot avoid an old pair of concepts from&#xA;scheduling theory, cooperative versus preemptive. Cooperative scheduling relies on the scheduled&#xA;party voluntarily yielding; preemptive scheduling relies on the scheduler interrupting the&#xA;scheduled party from the outside.&lt;/p&gt;&#xA;&lt;p&gt;The Go runtime has nothing like the hardware interrupt capability of an operating system kernel.&#xA;The work-stealing scheduler (&lt;a href=&#34;.././steal&#34;&gt;9.2&lt;/a&gt;) is essentially first-come-first-served cooperative&#xA;scheduling. How it can still forcibly interrupt a G that refuses to yield, without sacrificing this&#xA;premise, is the design this section sets out to make clear. The thread starts from a theoretical&#xA;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>/en/part3concurrency/ch09sched/sysmon/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/sysmon/</guid>
      <description>&lt;h1 id=&#34;98-system-monitoring&#34;&gt;9.8 System Monitoring&lt;/h1&gt;&#xA;&lt;p&gt;The scheduler&amp;rsquo;s ordinary path was laid out in &lt;a href=&#34;.././schedule&#34;&gt;9.4 The Scheduling Loop&lt;/a&gt;: one M binds to one P,&#xA;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.&#xA;Yet once all the Ps are mired in long system calls, or some Goroutine spins forever and holds a P hostage, ordinary scheduling&#xA;seizes up. No one takes the P back, and no one polls the network. Put differently, cooperative logic that runs on a P&#xA;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>/en/part3concurrency/ch09sched/poller/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/poller/</guid>
      <description>&lt;h1 id=&#34;99-the-network-poller&#34;&gt;9.9 The Network Poller&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Source facts verified against &lt;code&gt;src/runtime/netpoll.go&lt;/code&gt; and its per-platform&#xA;implementations (&lt;code&gt;netpoll_epoll.go&lt;/code&gt;, &lt;code&gt;netpoll_kqueue.go&lt;/code&gt;, and so on) and&#xA;&lt;code&gt;src/internal/poll/fd_unix.go&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&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.&#xA;But if it truly blocked the operating system thread it runs on, then ten thousand&#xA;goroutines waiting on the network would tie up ten thousand threads, and the M:N model&#xA;that &lt;a href=&#34;.././model&#34;&gt;9.1&lt;/a&gt; worked so hard to build would collapse in an instant. What lets&#xA;the blocking style still scale is the network poller (netpoller). Behind it lies a long&#xA;history about &amp;ldquo;how to tend a vast number of connections with a handful of threads.&amp;rdquo; This&#xA;section first lays out that history and the design axes behind it, then looks at how Go&#xA;hides a mature event mechanism inside the runtime so that you write synchronous code and&#xA;run event-driven I/O.&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.10 Timers</title>
      <link>/en/part3concurrency/ch09sched/timer/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/timer/</guid>
      <description>&lt;h1 id=&#34;910-timers&#34;&gt;9.10 Timers&lt;/h1&gt;&#xA;&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&#xA;all rest on the same timer machinery. It has to answer a question that looks simple but is in fact subtle, a&#xA;question about data structures: when thousands of timers exist at once, how do we efficiently know &amp;ldquo;who should&#xA;be woken next, and when,&amp;rdquo; without burning a thread to do it. This section starts from that abstract problem,&#xA;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>/en/part3concurrency/ch09sched/numa/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/en/part3concurrency/ch09sched/numa/</guid>
      <description>&lt;h1 id=&#34;911-numa-awareness-and-the-future-of-the-scheduler&#34;&gt;9.11 NUMA Awareness and the Future of the Scheduler&lt;/h1&gt;&#xA;&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>
