<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chinmay Pandya</title>
    <description>The latest articles on DEV Community by Chinmay Pandya (@chinmay_pandya_971976cf5e).</description>
    <link>https://dev.to/chinmay_pandya_971976cf5e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1661325%2F6f88ab47-2752-4cc7-82b7-66d176684d0c.png</url>
      <title>DEV Community: Chinmay Pandya</title>
      <link>https://dev.to/chinmay_pandya_971976cf5e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chinmay_pandya_971976cf5e"/>
    <language>en</language>
    <item>
      <title>4 Lines of Go Taught Me More About CPU, Threads and Memory Than Hours of Theory</title>
      <dc:creator>Chinmay Pandya</dc:creator>
      <pubDate>Wed, 30 Sep 2026 10:42:28 +0000</pubDate>
      <link>https://dev.to/chinmay_pandya_971976cf5e/4-lines-of-go-taught-me-more-about-cpu-threads-and-memory-than-hours-of-theory-3f27</link>
      <guid>https://dev.to/chinmay_pandya_971976cf5e/4-lines-of-go-taught-me-more-about-cpu-threads-and-memory-than-hours-of-theory-3f27</guid>
      <description>&lt;p&gt;I was looking at Activity Monitor while running this Go program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is almost nothing here.&lt;/p&gt;

&lt;p&gt;Yet Activity Monitor showed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threads&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context switches&lt;/td&gt;
&lt;td&gt;22K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real memory&lt;/td&gt;
&lt;td&gt;3.2 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared memory&lt;/td&gt;
&lt;td&gt;700 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Virtual memory&lt;/td&gt;
&lt;td&gt;~400 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgt9ob84zqg364ocb61mu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgt9ob84zqg364ocb61mu.png" alt="Image shows CPU utilization and memory allocation and analysis for the process" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That made me ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If my code is almost empty, why does the operating system have so much going on?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why is CPU at 100%?
&lt;/h2&gt;

&lt;p&gt;The answer is the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no &lt;code&gt;sleep&lt;/code&gt;, &lt;code&gt;wait&lt;/code&gt;, I/O, or other blocking operation.&lt;/p&gt;

&lt;p&gt;The CPU finishes one iteration and immediately starts another.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        ┌───────────┐
        │   for { } │
        └─────┬─────┘
              │
              ▼
        Execute again
              │
              └──────────┐
                         ▼
                    Execute again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goroutine is always &lt;strong&gt;runnable&lt;/strong&gt;, so the CPU always has work to execute.&lt;/p&gt;

&lt;p&gt;That is why the process can consume approximately &lt;strong&gt;100% of one logical CPU&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The important part
&lt;/h3&gt;

&lt;p&gt;Our code never says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Give me a CPU."
"Create a thread."
"Run me on CPU core 2."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those decisions are handled underneath our code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqqnbdpv30r2o5tk0q2i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqqnbdpv30r2o5tk0q2i.png" alt="Image shows thread, port and context switching metrics for the process" width="799" height="560"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Why do we need a Go runtime?
&lt;/h2&gt;

&lt;p&gt;Our code describes the work.&lt;/p&gt;

&lt;p&gt;It does not implement all the machinery required to keep that work running.&lt;/p&gt;

&lt;p&gt;For example, if we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we don't manually write code to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create an OS thread
Find a CPU
Schedule the work
Manage its stack
Handle blocking
Manage memory
Run garbage collection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;Go runtime&lt;/strong&gt; provides this machinery.&lt;/p&gt;

&lt;p&gt;A simplified view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────┐
│      Go Runtime     │
├─────────────────────┤
│ Scheduler           │
│ Goroutine management│
│ Memory management   │
│ Garbage collection  │
│ Thread management   │
└─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the running program contains more than the code we wrote.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why do we need a scheduler?
&lt;/h2&gt;

&lt;p&gt;Our code says &lt;strong&gt;what work exists&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It doesn't decide &lt;strong&gt;when or where that work runs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Go programs can have thousands of goroutines, while a machine has a much smaller number of CPU cores.&lt;/p&gt;

&lt;p&gt;The Go scheduler manages that mismatch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Many goroutines
      │
      ▼
┌───────────────┐
│ Go Scheduler  │
└───────┬───────┘
        │
        ▼
 Fewer OS threads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scheduler decides which runnable goroutine should execute on an available OS thread.&lt;/p&gt;

&lt;p&gt;This is why we can write concurrent Go code without manually creating and managing an OS thread for every piece of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Why are there 5 threads if I never created them?
&lt;/h2&gt;

&lt;p&gt;Activity Monitor showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Threads: 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But our code contains no thread creation.&lt;/p&gt;

&lt;p&gt;That's because &lt;strong&gt;goroutines are not OS threads&lt;/strong&gt;. Our &lt;code&gt;main()&lt;/code&gt; code runs as the main goroutine and it is not a 1:1 mapping with OS threads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine
    │
    ▼
Go Runtime
    │
    ▼
OS Thread
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We create and manage goroutines at the Go level.&lt;/p&gt;

&lt;p&gt;The Go runtime manages OS threads underneath them.&lt;/p&gt;

&lt;p&gt;The OS then manages those threads.&lt;/p&gt;

&lt;p&gt;So the 5 threads are part of the machinery used to run the process; they weren't five threads explicitly created in our source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Why do we need an OS scheduler?
&lt;/h2&gt;

&lt;p&gt;The Go scheduler is not the final scheduler.&lt;/p&gt;

&lt;p&gt;The machine may have threads from many different processes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chrome
VS Code
Go
Terminal
System processes
      │
      ▼
┌───────────────┐
│ OS Scheduler  │
└───────┬───────┘
        │
        ▼
       CPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OS scheduler decides which OS thread gets to execute and when.&lt;/p&gt;

&lt;p&gt;This lets many threads share a limited number of CPU cores.&lt;/p&gt;

&lt;p&gt;Our Go code does not need to know which CPU core is currently executing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Why are there context switches?
&lt;/h2&gt;

&lt;p&gt;If several threads need the CPU, the OS has to switch between them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread A
   │
   ▼
Save state
   │
   ▼
Thread B
   │
   ▼
Save state
   │
   ▼
Thread C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;context switch&lt;/strong&gt; is the transition from one execution context to another.&lt;/p&gt;

&lt;p&gt;The OS saves enough state to later resume the previous thread, such as CPU registers and its instruction location.&lt;/p&gt;

&lt;p&gt;Activity Monitor showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context switches: 22K
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the process was involved in many such switches during the measurement period.&lt;/p&gt;

&lt;p&gt;Context switches are normal; excessive switching can become a performance cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Why does an almost-empty program use 3.2 MB of RAM?
&lt;/h2&gt;

&lt;p&gt;The source code is tiny.&lt;/p&gt;

&lt;p&gt;The running process is not.&lt;/p&gt;

&lt;p&gt;A Go process also contains things such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────┐
│    Go Process    │
├──────────────────┤
│ Your code        │
│ Go runtime       │
│ Goroutine stacks │
│ Heap             │
│ Libraries        │
│ Other data       │
└──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operating system keeps currently needed parts in physical memory.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source code size ≠ Process memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;3.2 MB real memory&lt;/strong&gt; is the physical memory currently resident for the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Why is some memory shared?
&lt;/h2&gt;

&lt;p&gt;Activity Monitor showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shared memory: 700 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Processes can use the same physical memory pages.&lt;/p&gt;

&lt;p&gt;For example, multiple processes may use pages belonging to the same shared library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Process A ──┐
Process B ──┼──► Shared pages
Process C ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same physical pages can therefore be mapped into multiple processes.&lt;/p&gt;

&lt;p&gt;The 700 KB does not mean our program explicitly created a 700 KB shared-memory buffer.&lt;/p&gt;

&lt;p&gt;It is memory classified as shared for the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Why is virtual memory ~400 GB?
&lt;/h2&gt;

&lt;p&gt;This was the strangest number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Virtual memory ≈ 400 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My laptop obviously doesn't have 400 GB of RAM.&lt;/p&gt;

&lt;p&gt;That's because &lt;strong&gt;virtual memory is not physical RAM&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Programs work with virtual addresses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Program
   │
   ▼
Virtual address
   │
   ▼
CPU / MMU
   │
   ▼
Physical memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OS and CPU translate virtual addresses to physical memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  A simple analogy
&lt;/h3&gt;

&lt;p&gt;A 10-digit phone-number system has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000000000 → 9999999999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a huge range of possible numbers.&lt;/p&gt;

&lt;p&gt;It doesn't mean that every number has an active SIM card.&lt;/p&gt;

&lt;p&gt;Virtual address space works similarly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────────┐
│     Virtual address       │
│          space            │
├───────────────────────────┤
│ Mapped regions            │
│ Reserved regions          │
│ Unused regions            │
└───────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A process can therefore have a large virtual-memory footprint without using the same amount of physical RAM.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~400 GB virtual memory
          ≠
~400 GB RAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;real memory&lt;/strong&gt; number is the one describing currently resident physical memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model
&lt;/h2&gt;

&lt;p&gt;The whole experiment can be reduced to two flows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code
 │
 ▼
Go Runtime
 │
 ▼
OS
 │
 ▼
CPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runtime and OS provide the machinery that our source code doesn't explicitly manage.&lt;/p&gt;

&lt;p&gt;For memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Virtual address
      │
      ▼
   CPU / MMU
      │
      ▼
Physical memory
      │
      ▼
     RAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that explains why four lines of Go can produce all those Activity Monitor numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I saw&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;100% CPU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The loop is continuously runnable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5 threads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The runtime and OS manage execution using OS threads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;22K context switches&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Threads are switched during execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3.2 MB real memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The running process needs resident physical memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;700 KB shared memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Some physical pages can be shared&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;~400 GB virtual memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Virtual-memory accounting is separate from physical RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A tiny program doesn't mean a tiny execution environment.&lt;/p&gt;

&lt;p&gt;The code can be four lines while the machinery underneath it involves &lt;strong&gt;runtime, goroutines, threads, scheduling, virtual memory and physical memory&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
      <category>monitoring</category>
      <category>computerscience</category>
    </item>
  </channel>
</rss>
