<?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: ThinkerQAQ</title>
    <description>The latest articles on DEV Community by ThinkerQAQ (@thinkerqaq).</description>
    <link>https://dev.to/thinkerqaq</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%2F4126248%2F88bfd2a0-6d32-44f9-9b55-9457abd75c1a.jpeg</url>
      <title>DEV Community: ThinkerQAQ</title>
      <link>https://dev.to/thinkerqaq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thinkerqaq"/>
    <language>en</language>
    <item>
      <title>Concurrency Programming (1.5): The Operating System Layer — Threads, Schedulers, and Runtime Scheduling</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Sun, 11 Oct 2026 13:24:01 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/concurrency-programming-15-the-operating-system-layer-threads-schedulers-and-runtime-13p9</link>
      <guid>https://dev.to/thinkerqaq/concurrency-programming-15-the-operating-system-layer-threads-schedulers-and-runtime-13p9</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;0. From Language-Level Execution Units to the CPU&lt;/li&gt;
&lt;li&gt;
1. How Does Java Schedule Threads?

&lt;ul&gt;
&lt;li&gt;1.1 From Platform Threads to Virtual Threads&lt;/li&gt;
&lt;li&gt;1.1.1 The Simplest Model: One Java Thread per OS Thread&lt;/li&gt;
&lt;li&gt;1.1.2 The Problem: Resource and Scheduling Costs of Many OS Threads&lt;/li&gt;
&lt;li&gt;1.1.3 The Solution: Let the JVM Schedule Lightweight Threads&lt;/li&gt;
&lt;li&gt;1.1.4 The Result: Platform Threads and Virtual Threads&lt;/li&gt;
&lt;li&gt;1.2 Execution Layers&lt;/li&gt;
&lt;li&gt;1.3 From Java Thread Lifecycles to Their Implementation&lt;/li&gt;
&lt;li&gt;1.3.1 Creation and Start: Created → Runnable&lt;/li&gt;
&lt;li&gt;1.3.2 Waiting to Be Scheduled: Runnable&lt;/li&gt;
&lt;li&gt;1.3.3 Running: Runnable → Running&lt;/li&gt;
&lt;li&gt;1.3.4 Parking and Waiting: Running → Waiting&lt;/li&gt;
&lt;li&gt;1.3.5 Wakeup and Resumption: Waiting → Runnable → Running&lt;/li&gt;
&lt;li&gt;1.3.6 Completion and Cleanup: Running → Terminated&lt;/li&gt;
&lt;li&gt;1.4 End-to-End Sequence Diagrams&lt;/li&gt;
&lt;li&gt;1.5 Platform Threads vs. Virtual Threads&lt;/li&gt;
&lt;li&gt;1.6 Returning to the Opening Question&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
2. How Does Go Schedule Goroutines?

&lt;ul&gt;
&lt;li&gt;2.1 From G-M to G-M-P&lt;/li&gt;
&lt;li&gt;2.1.1 The Simplest Model: A Goroutine Runs on an OS Thread&lt;/li&gt;
&lt;li&gt;2.1.2 The Problem: Many Goroutines, Fewer OS Threads&lt;/li&gt;
&lt;li&gt;2.1.3 The Solution: Evolving from G-M to G-M-P&lt;/li&gt;
&lt;li&gt;2.1.4 The Final Model: G, M, and P&lt;/li&gt;
&lt;li&gt;2.2 Execution Layers&lt;/li&gt;
&lt;li&gt;2.3 From a Goroutine's Lifecycle to Its Runtime Implementation&lt;/li&gt;
&lt;li&gt;2.3.1 Creation: Created → Runnable&lt;/li&gt;
&lt;li&gt;2.3.2 Waiting for Scheduling: Runnable&lt;/li&gt;
&lt;li&gt;2.3.3 Execution: Runnable → Running&lt;/li&gt;
&lt;li&gt;2.3.4 Parking and Waiting: Running → Waiting&lt;/li&gt;
&lt;li&gt;2.3.5 Wakeup and Resumption: Waiting → Runnable → Running&lt;/li&gt;
&lt;li&gt;2.3.6 System Calls and Preemption&lt;/li&gt;
&lt;li&gt;2.3.7 Completion and Cleanup: Running → Dead&lt;/li&gt;
&lt;li&gt;2.4 End-to-End Sequence Diagram&lt;/li&gt;
&lt;li&gt;2.5 Returning to the Opening Question&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
3. How Does CPython Schedule Threads?

&lt;ul&gt;
&lt;li&gt;3.1 From Native OS Threads to the GIL and Free-Threaded Builds&lt;/li&gt;
&lt;li&gt;3.1.1 The Simplest Model: One Python Thread per OS Thread&lt;/li&gt;
&lt;li&gt;3.1.2 The Problem: Sharing Interpreter State Across OS Threads&lt;/li&gt;
&lt;li&gt;3.1.3 The Default Solution: The GIL Restricts Interpreter Execution&lt;/li&gt;
&lt;li&gt;3.1.4 The Next Step: Free-Threaded CPython&lt;/li&gt;
&lt;li&gt;3.1.5 The Final Model: Thread, PyThreadState, and GIL&lt;/li&gt;
&lt;li&gt;3.2 Execution Layers&lt;/li&gt;
&lt;li&gt;3.3 From CPython Thread Lifecycles to Their Implementation&lt;/li&gt;
&lt;li&gt;3.3.1 Creation and Start: Created → Runnable&lt;/li&gt;
&lt;li&gt;3.3.2 Waiting for Scheduling: Runnable&lt;/li&gt;
&lt;li&gt;3.3.3 Execution: Runnable → Running&lt;/li&gt;
&lt;li&gt;3.3.4 Parking and Waiting: Running → Waiting&lt;/li&gt;
&lt;li&gt;3.3.5 Wakeup and Resumption: Waiting → Runnable → Running&lt;/li&gt;
&lt;li&gt;3.3.6 Completion and Cleanup: Running → Terminated&lt;/li&gt;
&lt;li&gt;3.4 End-to-End Sequence Diagram&lt;/li&gt;
&lt;li&gt;3.5 Returning to the Opening Question&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
4. What the Three Implementations Have in Common

&lt;ul&gt;
&lt;li&gt;4.1 How Does an Execution Unit Reach the CPU? — Establish Its Carrier&lt;/li&gt;
&lt;li&gt;4.2 Who Selects What Runs Next? — Separate the Scheduling Layers&lt;/li&gt;
&lt;li&gt;4.3 What Happens After a Wait? — Regain an Execution Opportunity&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - 5. Next: Language Memory Models
&lt;/h2&gt;

&lt;h2&gt;
  
  
  0. From Language-Level Execution Units to the CPU
&lt;/h2&gt;

&lt;p&gt;The previous article examined how the CPU executes &lt;code&gt;count++&lt;/code&gt; and how atomic instructions, cache coherence, and fences provide the hardware foundations for atomicity, visibility, and ordering.&lt;/p&gt;

&lt;p&gt;This article moves one layer up, to the operating system. It asks one central question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How do Java threads, Go goroutines, and CPython threads pass through their runtimes and the operating system to execute on a CPU?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Answering it requires understanding three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Execution mapping:&lt;/strong&gt; How does a language-level execution unit map to an OS thread?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling responsibilities:&lt;/strong&gt; What do the runtime scheduler and the operating system scheduler each control?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Waiting and resumption:&lt;/strong&gt; When an execution unit waits, wakes, or resumes, what is suspended, and which threads can continue running?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, we will examine Java, Go, and CPython in turn. For each, we will trace the evolution of its threading model, then use execution layers, lifecycles, and sequence diagrams to answer these three questions. Finally, we will draw out the language-independent mechanisms they share and return to the opening question.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. How Does Java Schedule Threads?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 From Platform Threads to Virtual Threads
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1.1.1 The Simplest Model: One Java Thread per OS Thread
&lt;/h4&gt;

&lt;p&gt;When a program has only a few concurrent tasks, the most straightforward approach is to map each Java thread to an OS thread. Java creates the thread, and the operating system schedules the corresponding OS thread.&lt;/p&gt;

&lt;p&gt;With relatively few threads, this one-to-one mapping is easy to understand and debug.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.1.2 The Problem: Resource and Scheduling Costs of Many OS Threads
&lt;/h4&gt;

&lt;p&gt;Suppose a service handles many requests, each on its own thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request 1 → Thread 1 → OS Thread 1
Request 2 → Thread 2 → OS Thread 2
Request 3 → Thread 3 → OS Thread 3
...
Request N → Thread N → OS Thread N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many request-handling tasks spend most of their time waiting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read from a database
Wait for a network response
Wait for a message
Wait for other I/O
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Java threads continue to map one-to-one to OS threads, two costs become significant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Native-thread resources:&lt;/strong&gt; Even while a task is waiting, the operating system must retain resources such as its kernel thread and native stack. These costs grow with the thread count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling overhead:&lt;/strong&gt; When many threads wake frequently and become Runnable, the operating system must schedule among more runnable threads. Context switches and scheduler work consume additional CPU time.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  1.1.3 The Solution: Let the JVM Schedule Lightweight Threads
&lt;/h4&gt;

&lt;p&gt;How can we address these costs?&lt;/p&gt;

&lt;p&gt;In this setting, logical tasks spend most of their time waiting for I/O and relatively little time using the CPU. They do not need to occupy a dedicated OS thread for their entire lifetime. The key is to &lt;strong&gt;decouple logical execution units from OS threads&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once they are decoupled, the JVM can introduce its own scheduler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logical Thread A ─┐
Logical Thread B ─┼─→ JVM Scheduler ─→ Carrier Thread 1 ⇔ OS Thread 1
Logical Thread C ─┤                  └→ Carrier Thread 2 ⇔ OS Thread 2
Logical Thread D ─┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Carrier Thread is a Platform Thread in the carrier role. The ⇔ symbol indicates its one-to-one native mapping to an OS thread, not a method call.&lt;/p&gt;

&lt;p&gt;The JVM can manage many logical threads while the operating system schedules a smaller number of underlying OS threads.&lt;/p&gt;

&lt;p&gt;Java calls this lightweight logical thread a &lt;strong&gt;Virtual Thread&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.1.4 The Result: Platform Threads and Virtual Threads
&lt;/h4&gt;

&lt;p&gt;A Platform Thread maps one-to-one to an OS Thread; the JVM Scheduler dispatches Virtual Threads onto Platform Threads acting as carriers, and the OS Scheduler schedules the underlying OS Threads.&lt;/p&gt;




&lt;h3&gt;
  
  
  1.2 Execution Layers
&lt;/h3&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%2F9ndrji4t5vm88n7thhhp.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%2F9ndrji4t5vm88n7thhhp.png" alt="A platform JavaThread maps one-to-one to an OS thread. The JVM Scheduler dispatches a VirtualThread continuation to a carrier platform thread, where it is mounted and executed. The OS scheduler ultimately schedules the underlying OS thread on a CPU." width="800" height="1318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 From Java Thread Lifecycles to Their Implementation
&lt;/h3&gt;

&lt;p&gt;A Java thread's conceptual lifecycle is:&lt;/p&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%2F8ppz4fzvv4uyo8wc01jj.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%2F8ppz4fzvv4uyo8wc01jj.png" alt="A thread is created, becomes Runnable after starting, and runs when scheduled. It may wait, wake, and be scheduled again before eventually terminating." width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a conceptual lifecycle, not an exact mapping to Java's &lt;code&gt;Thread.State&lt;/code&gt;. In particular, &lt;code&gt;RUNNABLE&lt;/code&gt; covers both threads awaiting CPU time and threads currently running.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.3.1 Creation and Start: Created → Runnable
&lt;/h4&gt;

&lt;p&gt;A platform thread creates an OS thread at &lt;code&gt;start()&lt;/code&gt;; a virtual thread submits its continuation to the JVM scheduler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(1) Platform Thread&lt;/strong&gt;&lt;/p&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%2Fvjvfw6a3f8bkf6r3ugzw.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%2Fvjvfw6a3f8bkf6r3ugzw.png" alt="HotSpot creates a JavaThread and native OS Thread when a platform thread starts." width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(2) Virtual Thread&lt;/strong&gt;&lt;/p&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%2F3kzb2rgdjzvwfl7ip3dy.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%2F3kzb2rgdjzvwfl7ip3dy.png" alt="A virtual thread starts by submitting its Continuation to the JVM scheduler." width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  1.3.2 Waiting to Be Scheduled: Runnable
&lt;/h4&gt;

&lt;p&gt;A platform thread waits for OS scheduling; a virtual thread waits for JVM dispatch to a carrier thread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(1) Platform Thread&lt;/strong&gt;&lt;/p&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%2Fdtvmvgzotjxx77ucyo2d.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%2Fdtvmvgzotjxx77ucyo2d.png" alt="The platform OS Thread is runnable but not yet scheduled on a CPU." width="799" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(2) Virtual Thread&lt;/strong&gt;&lt;/p&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%2Fpif8ng5d4nadhw646358.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%2Fpif8ng5d4nadhw646358.png" alt="The virtual thread Continuation is queued, but carrier dispatch has not happened." width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  1.3.3 Running: Runnable → Running
&lt;/h4&gt;

&lt;p&gt;A platform thread executes &lt;code&gt;Thread.run()&lt;/code&gt; on its OS thread; a virtual thread mounts and runs its continuation on a carrier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(1) Platform Thread&lt;/strong&gt;&lt;/p&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%2Fxhjnz7kkretmzyn1sjh0.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%2Fxhjnz7kkretmzyn1sjh0.png" alt="The OS scheduler runs the native thread, which enters Java user code through HotSpot." width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(2) Virtual Thread&lt;/strong&gt;&lt;/p&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%2Fsvyvyfmpbv0jhfz9taof.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%2Fsvyvyfmpbv0jhfz9taof.png" alt="The JVM scheduler dispatches a Continuation, which mounts and runs on a Carrier." width="799" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  1.3.4 Parking and Waiting: Running → Waiting
&lt;/h4&gt;

&lt;p&gt;A waiting platform thread normally retains its OS thread; &lt;strong&gt;when unmounting is possible&lt;/strong&gt;, a virtual thread releases its carrier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(1) Platform Thread&lt;/strong&gt;&lt;/p&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%2Fbwri1y2w4dfsms4w4p02.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%2Fbwri1y2w4dfsms4w4p02.png" alt="A waiting platform thread normally blocks its underlying OS Thread." width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(2) Virtual Thread&lt;/strong&gt;&lt;/p&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%2Fpl4gva96um4paazrsemz.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%2Fpl4gva96um4paazrsemz.png" alt="If the wait permits unmounting, the virtual thread parks and frees the carrier." width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  1.3.5 Wakeup and Resumption: Waiting → Runnable → Running
&lt;/h4&gt;

&lt;p&gt;A platform thread resumes on the same OS thread; a virtual thread reenters the JVM scheduler and may resume on a different carrier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(1) Platform Thread&lt;/strong&gt;&lt;/p&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%2F25qzxd1uvb4oxe3bo3je.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%2F25qzxd1uvb4oxe3bo3je.png" alt="The platform OS Thread becomes runnable and resumes after scheduling." width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(2) Virtual Thread&lt;/strong&gt;&lt;/p&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%2Fj6q941cyajug8txy2bez.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%2Fj6q941cyajug8txy2bez.png" alt="The virtual thread requeues, and may mount on a different Carrier to resume." width="799" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  1.3.6 Completion and Cleanup: Running → Terminated
&lt;/h4&gt;

&lt;p&gt;A platform thread cleans up its JavaThread and OS thread; a virtual thread terminates without terminating its carrier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(1) Platform Thread&lt;/strong&gt;&lt;/p&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%2Fsdsjnx9p3obpb4og50f0.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%2Fsdsjnx9p3obpb4og50f0.png" alt="The platform thread completes its exit path and native OS Thread cleanup." width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(2) Virtual Thread&lt;/strong&gt;&lt;/p&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%2Fhmkhjsfanaurrb0all14.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%2Fhmkhjsfanaurrb0all14.png" alt="The completed virtual thread terminates without terminating its Carrier." width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.4 End-to-End Sequence Diagrams
&lt;/h3&gt;

&lt;p&gt;The following diagrams trace the native startup path for a platform thread and the essential mount, unmount, and resume operations for a virtual thread. The virtual-thread sequence assumes its carrier already has CPU time, so it does not repeat OS scheduling.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.4.1 Platform Thread
&lt;/h4&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%2F33lg876eboezafzx1bnc.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%2F33lg876eboezafzx1bnc.png" alt="Thread.start creates an OS thread through HotSpot. Once scheduled, it executes Thread.run. A blocking wait suspends that OS thread. When scheduled again, the thread continues where it stopped instead of repeating the startup routine." width="800" height="914"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  1.4.2 Virtual Thread
&lt;/h4&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%2Fqjqyvnvxmd15uj3g3r2a.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%2Fqjqyvnvxmd15uj3g3r2a.png" alt="A virtual thread is submitted to the JVM scheduler. A carrier with CPU time runs and mounts its continuation. A wait that permits unmounting frees the carrier to do other work. The virtual thread may later resume on a different carrier." width="800" height="885"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.5 Platform Threads vs. Virtual Threads
&lt;/h3&gt;

&lt;p&gt;Their lifecycle differences can be summarized as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lifecycle phase&lt;/th&gt;
&lt;th&gt;Platform Thread&lt;/th&gt;
&lt;th&gt;Virtual Thread&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Created → Runnable&lt;/td&gt;
&lt;td&gt;The JVM creates an OS thread&lt;/td&gt;
&lt;td&gt;The JVM submits a continuation to its scheduler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runnable&lt;/td&gt;
&lt;td&gt;Waits for the OS scheduler&lt;/td&gt;
&lt;td&gt;The JVM scheduler dispatches it to a carrier; the OS scheduler schedules the carrier's OS thread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Running&lt;/td&gt;
&lt;td&gt;The OS thread executes Java code directly&lt;/td&gt;
&lt;td&gt;The virtual thread mounts on a Carrier Thread backed by an OS Thread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Waiting&lt;/td&gt;
&lt;td&gt;The OS thread generally waits as well&lt;/td&gt;
&lt;td&gt;The virtual thread can wait independently and release its carrier if it can unmount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resume&lt;/td&gt;
&lt;td&gt;The OS thread becomes runnable again&lt;/td&gt;
&lt;td&gt;The virtual thread is resubmitted to the JVM scheduler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminated&lt;/td&gt;
&lt;td&gt;JavaThread / OS thread exit&lt;/td&gt;
&lt;td&gt;Its continuation completes and &lt;code&gt;afterDone()&lt;/code&gt; moves it to TERMINATED&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The central distinction is the execution relationship: platform threads remain associated with their OS threads, while the JVM dispatches virtual threads to carriers. When a virtual thread can unmount, its carrier becomes available for other tasks.&lt;/p&gt;

&lt;p&gt;For implementation details, see OpenJDK's &lt;a href="https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Thread.java" rel="noopener noreferrer"&gt;&lt;code&gt;Thread.java&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/prims/jvm.cpp" rel="noopener noreferrer"&gt;&lt;code&gt;jvm.cpp&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/javaThread.cpp" rel="noopener noreferrer"&gt;&lt;code&gt;javaThread.cpp&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java" rel="noopener noreferrer"&gt;&lt;code&gt;VirtualThread.java&lt;/code&gt;&lt;/a&gt;. For the virtual-thread design, see &lt;a href="https://openjdk.org/jeps/444" rel="noopener noreferrer"&gt;JEP 444&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.6 Returning to the Opening Question
&lt;/h3&gt;

&lt;p&gt;As Section 1.2, Execution Layers shows, a Platform Thread maps one-to-one to an OS Thread, while a Virtual Thread reaches an OS Thread through a Carrier Thread. &lt;strong&gt;Therefore, both thread models ultimately execute on OS Threads.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Section 1.3.2, Waiting to Be Scheduled and Section 1.3.3, Running show that the JVM Scheduler dispatches Virtual Threads to Carrier Threads, while the OS Scheduler schedules the underlying OS Threads. &lt;strong&gt;Therefore, the scheduling paths differ, but the OS Scheduler ultimately grants CPU execution time to both.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Section 1.3.4, Parking and Waiting and Section 1.3.5, Wakeup and Resumption show that a Virtual Thread can release its Carrier Thread when waiting permits unmounting, then reenter JVM scheduling when ready. &lt;strong&gt;Therefore, many waiting tasks need not each occupy an OS Thread for their entire lifetime.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answering the opening question:&lt;/strong&gt; Java retains the native-thread model of Platform Threads while allowing Virtual Threads to reuse Carrier Threads, so many concurrent tasks can ultimately execute on the CPU using a relatively small number of OS Threads.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. How Does Go Schedule Goroutines?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 From G-M to G-M-P
&lt;/h3&gt;

&lt;h4&gt;
  
  
  2.1.1 The Simplest Model: A Goroutine Runs on an OS Thread
&lt;/h4&gt;

&lt;p&gt;For a small number of concurrent tasks, the simplest approach is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine
    ↓
OS Thread
    ↓
Operating System Scheduler
    ↓
CPU Core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One Goroutine maps to one OS Thread; the Go runtime manages the Goroutine, while the operating system schedules its OS Thread.&lt;/p&gt;

&lt;p&gt;At modest thread counts, this one-to-one model is easy to understand and debug.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.1.2 The Problem: Many Goroutines, Fewer OS Threads
&lt;/h4&gt;

&lt;p&gt;Suppose a service handles many requests, each on its own Goroutine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request 1 → Goroutine 1 → OS Thread 1
Request 2 → Goroutine 2 → OS Thread 2
Request 3 → Goroutine 3 → OS Thread 3
...
Request N → Goroutine N → OS Thread N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many service tasks spend most of their time waiting for databases, network responses, or other I/O.&lt;/p&gt;

&lt;p&gt;If Goroutines remained mapped one-to-one to OS Threads, they would face the same two costs as Java Platform Threads:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Native-thread resources:&lt;/strong&gt; a waiting task would still retain a kernel thread, native stack, and other resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling overhead:&lt;/strong&gt; many OS Threads repeatedly becoming runnable would increase context switching and OS scheduling work.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  2.1.3 The Solution: Evolving from G-M to G-M-P
&lt;/h4&gt;

&lt;p&gt;How can the runtime address this?&lt;/p&gt;

&lt;p&gt;These logical tasks spend much of their time waiting for I/O rather than using the CPU. They therefore do not need to retain a dedicated OS Thread for their entire lifetime.&lt;/p&gt;

&lt;p&gt;The solution is to decouple the &lt;code&gt;logical execution unit&lt;/code&gt; from the &lt;code&gt;OS Thread&lt;/code&gt;: the Go runtime schedules Goroutines so a limited number of OS Threads can be reused.&lt;/p&gt;

&lt;h5&gt;
  
  
  2.1.3.1 Step One: G-M with a Global Run Queue
&lt;/h5&gt;

&lt;p&gt;Start with G, M, and a global run queue:&lt;/p&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%2Ff1zn33czmnaahitovsec.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%2Ff1zn33czmnaahitovsec.png" alt="G1 through G4 enter one global run queue. Both M0 and M1 draw runnable goroutines from it, so they may contend for the same queue." width="452" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, G is a goroutine and M is an OS thread.&lt;/p&gt;

&lt;p&gt;Here, runnable Gs share a global run queue, and multiple Ms fetch work from it.&lt;/p&gt;

&lt;p&gt;As concurrency grows, two problems emerge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Multiple M instances
  ↓
Access the same Global Run Queue
  ↓
More synchronization contention
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scheduling state for G is centralized
        ↓
Poorer locality
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To reduce contention on the global queue, some scheduling state and runnable work can be distributed across execution contexts:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What if each execution context had its own scheduling resources and local run queue?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5&gt;
  
  
  2.1.3.2 Step Two: Introduce P and Local Run Queues
&lt;/h5&gt;

&lt;p&gt;To reduce contention for the global queue, the Go runtime introduces &lt;strong&gt;P&lt;/strong&gt;, with each P maintaining its own local run queue:&lt;/p&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%2Fgh3dq1m5tmq9p8z4e7zs.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%2Fgh3dq1m5tmq9p8z4e7zs.png" alt="In G-M-P, P0 and P1 each own a local run queue. This snapshot shows G1 and G2 in P0's queue, executed by M0 holding P0, and G3 and G4 in P1's queue, executed by M1 holding P1. The queues are illustrative: a goroutine is not permanently tied to one P." width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;P holds the runtime resources needed to execute Go user code and maintains its own local run queue. The actual G-M-P scheduler also retains a global queue; goroutines are not permanently assigned to a particular P. We will examine other work sources next.&lt;/p&gt;

&lt;p&gt;This gives us the defining constraint of G-M-P:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;An M needs a P to execute Go user code.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Scheduling state is no longer concentrated in one queue contested by all M instances; each P maintains its own local run queue.&lt;/p&gt;

&lt;h5&gt;
  
  
  2.1.3.3 Third Question: What If a P's Local Queue Is Empty?
&lt;/h5&gt;

&lt;p&gt;Local queues reduce contention but can leave work unevenly distributed: one P is busy while another has nothing queued. An M holding an idle P can use work stealing to obtain runnable goroutines from another P's queue.&lt;/p&gt;

&lt;p&gt;The scheduler can also find work from other sources:&lt;/p&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%2F68ypu27iaipqif9crpff.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%2F68ypu27iaipqif9crpff.png" alt="findRunnable searches for runnable goroutines using the local queue, global queue, work stolen from another P, network polling, and timers." width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the actual implementation, &lt;code&gt;findRunnable()&lt;/code&gt; considers fairness, timers, the network poller, and other conditions when searching for runnable Gs. It does not rigidly follow the same sequence every time.&lt;/p&gt;

&lt;h5&gt;
  
  
  2.1.3.4 Fourth Question: What If M Blocks in a System Call?
&lt;/h5&gt;

&lt;p&gt;Another constraint comes from blocking system calls:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If an M holding a P blocks in the OS, must the P become idle too?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If P remains permanently attached to that M, other goroutines cannot run using its scheduling resources while the M is blocked.&lt;/p&gt;

&lt;p&gt;P and M therefore need to be separable:&lt;/p&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%2Fp0nt4tqjhk2othixtju6.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%2Fp0nt4tqjhk2othixtju6.png" alt="Initially M0 holds P0 and runs G0. When M0 blocks in a system call, it still carries G0, but the runtime can reclaim P0 and hand it to M1 to run another goroutine. P need not remain idle until M0's system call returns." width="800" height="1345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;G0 remains blocked in the system call on M0's OS thread. Meanwhile the runtime can reclaim P0 so M1 can run another runnable G. When M0 returns from the system call, it must try to acquire a P again before resuming Go user code. P and M must therefore be free to separate and recombine.&lt;/p&gt;

&lt;h5&gt;
  
  
  2.1.3.5 Fifth Question: What If One G Runs for Too Long?
&lt;/h5&gt;

&lt;p&gt;Even without I/O, a goroutine that runs continuously for a long time can starve other runnable work of execution opportunities.&lt;/p&gt;

&lt;p&gt;The runtime therefore also needs &lt;strong&gt;preemption&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;G Running
    ↓
Preempt
    ↓
G Runnable
    ↓
Scheduler
    ↓
Another G gets CPU time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modern Go supports preemption, including asynchronous preemption, to reduce the chance that a long-running goroutine blocks progress elsewhere. Preemption does not, by itself, guarantee strict fairness.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.1.4 The Final Model: G, M, and P
&lt;/h4&gt;

&lt;h5&gt;
  
  
  2.1.4.1 G: Goroutine
&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;G&lt;/strong&gt; represents a goroutine: a logical execution unit managed by the Go runtime.&lt;/p&gt;

&lt;p&gt;It holds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stack;&lt;/li&gt;
&lt;li&gt;PC / scheduler context;&lt;/li&gt;
&lt;li&gt;Scheduling state such as Runnable, Running, and Waiting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;go f()&lt;/code&gt; creates a G, not a new OS thread.&lt;/p&gt;

&lt;h5&gt;
  
  
  2.1.4.2 M: Machine
&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;M&lt;/strong&gt; represents a real OS thread.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;M
↓
OS Thread
↓
OS Scheduler
↓
CPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OS scheduler schedules the OS thread represented by M onto a CPU.&lt;/p&gt;

&lt;p&gt;Each M also has a special &lt;strong&gt;g0&lt;/strong&gt; goroutine. Its system stack is used by the runtime for scheduling, stack management, and other low-level work.&lt;/p&gt;

&lt;h5&gt;
  
  
  2.1.4.3 P: Processor
&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;P&lt;/strong&gt; represents the runtime resources and execution capacity an M must hold to run Go code.&lt;/p&gt;

&lt;p&gt;It contains scheduler and allocator state, a local run queue, and &lt;code&gt;runnext&lt;/code&gt; for preferential scheduling.&lt;/p&gt;

&lt;p&gt;The number of P instances is determined by &lt;code&gt;GOMAXPROCS&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOMAXPROCS
    ↓
Number of P instances
    ↓
Maximum parallelism for Go user code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Their roles can be summarized in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;G is the task to run; M is the OS thread; P provides the runtime resources an M needs to execute Go code.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2.2 Execution Layers
&lt;/h3&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%2Fg2u127p0xv1kdpcvkb73.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%2Fg2u127p0xv1kdpcvkb73.png" alt="go f() is called at the language layer. The Go runtime represents the goroutine as G and uses P, run queues, and its scheduler to assign it to M. M represents an OS thread, which is ultimately scheduled onto a CPU by the OS scheduler." width="800" height="1556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Go runtime scheduler and OS scheduler operate at different layers. Switching goroutines on the same M does not necessarily involve an OS thread context switch.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 From a Goroutine's Lifecycle to Its Runtime Implementation
&lt;/h3&gt;

&lt;p&gt;A goroutine's conceptual lifecycle is:&lt;/p&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%2F43vhyotqtejnx4hbki3d.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%2F43vhyotqtejnx4hbki3d.png" alt="Once created, a goroutine becomes Runnable and is later scheduled to Running. It may wait, enter a system call, or be preempted and become runnable again before ultimately exiting as Dead." width="799" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram uses conceptual states such as Created and Waiting. The Go runtime uses more specific internal states, including &lt;code&gt;_Grunnable&lt;/code&gt; and &lt;code&gt;_Gwaiting&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3.1 Creation: Created → Runnable
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;go f()&lt;/code&gt; creates a G, marks it runnable, and enqueues it for scheduling.&lt;/p&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%2Faxhcs600l06f70zvdfle.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%2Faxhcs600l06f70zvdfle.png" alt=" raw `go f()` endraw  creates a G, marks it runnable, and enqueues it for scheduling." width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3.2 Waiting for Scheduling: Runnable
&lt;/h4&gt;

&lt;p&gt;The G is in a run queue, but an M holding a P has not yet selected it.&lt;/p&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%2F92a0ly8re0hbdwc4bdxw.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%2F92a0ly8re0hbdwc4bdxw.png" alt="The G is in a run queue, but an M holding a P has not yet selected it." width="798" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3.3 Execution: Runnable → Running
&lt;/h4&gt;

&lt;p&gt;An M holding a P selects a runnable G through the runtime scheduler, then runs it through &lt;code&gt;execute(G)&lt;/code&gt; and &lt;code&gt;gogo&lt;/code&gt;. The OS scheduler independently schedules M's underlying OS thread.&lt;/p&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%2Fnpmfwtqutg2mv47ddex8.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%2Fnpmfwtqutg2mv47ddex8.png" alt="An M holding a P selects G through the runtime scheduler and executes it with  raw `execute(G)` endraw  and  raw `gogo` endraw ; OS scheduling of M remains independent." width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3.4 Parking and Waiting: Running → Waiting
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;gopark()&lt;/code&gt; suspends only G, letting its M continue running other runnable goroutines while holding P.&lt;/p&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%2F0ax7f61rpb92qkhr70ma.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%2F0ax7f61rpb92qkhr70ma.png" alt=" raw `gopark()` endraw  suspends only G, letting its M continue running other runnable goroutines while holding P." width="799" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3.5 Wakeup and Resumption: Waiting → Runnable → Running
&lt;/h4&gt;

&lt;p&gt;When the wait ends, &lt;code&gt;goready()&lt;/code&gt; requeues G so the scheduler can dispatch it to an available M.&lt;/p&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%2Ftozfybxvho1m2qez4m0y.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%2Ftozfybxvho1m2qez4m0y.png" alt="When the wait ends,  raw `goready()` endraw  requeues G so the scheduler can dispatch it to an available M." width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3.6 System Calls and Preemption
&lt;/h4&gt;

&lt;p&gt;A system call can block M while P becomes available to another M; preemption makes a long-running G eligible for rescheduling.&lt;/p&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%2Fc402vmexrxd2a4yb86bo.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%2Fc402vmexrxd2a4yb86bo.png" alt="A system call can block M while P becomes available to another M; preemption makes a long-running G eligible for rescheduling." width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3.7 Completion and Cleanup: Running → Dead
&lt;/h4&gt;

&lt;p&gt;When G's function returns, the runtime cleans up G, marks it dead, and M schedules other work.&lt;/p&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%2Fcogwtio98cxnsy6kwyuf.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%2Fcogwtio98cxnsy6kwyuf.png" alt="When G's function returns, the runtime cleans up G, marks it dead, and M schedules other work." width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 End-to-End Sequence Diagram
&lt;/h3&gt;

&lt;p&gt;This sequence follows one G through creation, execution, waiting, and termination. OS scheduling and goroutine switching are separate layers: the diagram shows M being scheduled onto a CPU once, but later goroutine switches on that same M need not trigger another OS scheduling event.&lt;/p&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%2Fv7sik8rtabmxzrrwygmc.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%2Fv7sik8rtabmxzrrwygmc.png" alt="go f creates and queues a runnable G. An M already running on a CPU uses the Go runtime scheduler to select it from the work available to its P. gopark suspends the G, goready queues it again, and after exit the M looks for more work." width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For implementation details, see Go's &lt;a href="https://go.dev/src/runtime/proc.go" rel="noopener noreferrer"&gt;&lt;code&gt;proc.go&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://go.dev/src/runtime/runtime2.go" rel="noopener noreferrer"&gt;&lt;code&gt;runtime2.go&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.5 Returning to the Opening Question
&lt;/h3&gt;

&lt;p&gt;Section 2.1.4, The Final Model and Section 2.2, Execution Layers establish that G is a logical execution unit, M corresponds to an OS thread, and M needs P to execute Go code. &lt;strong&gt;Therefore, a G executes through an M holding a P, not as a dedicated OS thread.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Section 2.1.3.3, Work Sources, Section 2.3.2, Waiting for Scheduling, and Section 2.3.3, Execution show the Go scheduler selecting runnable Gs from queues, work stealing, or netpoll for an M, while the OS scheduler schedules the underlying OS thread. &lt;strong&gt;Therefore, the two schedulers decide which G runs and which OS thread receives CPU time, respectively.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Section 2.3.4, Parking and Section 2.3.5, Resumption show that M can run other Gs while one G waits. Section 2.3.6, System Calls and Preemption shows that another M may take P if a system call blocks the original M, and a long-running G may be preempted. &lt;strong&gt;Therefore, waiting or blocking need not leave the associated scheduling resources idle indefinitely.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answering the opening question:&lt;/strong&gt; Go uses G-M-P to schedule many goroutines on M instances holding P, while the OS scheduler runs their underlying OS threads on CPUs, allowing concurrent tasks to make progress on a limited number of CPU cores.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. How Does CPython Schedule Threads?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 From Native OS Threads to the GIL and Free-Threaded Builds
&lt;/h3&gt;

&lt;h4&gt;
  
  
  3.1.1 The Simplest Model: One Python Thread per OS Thread
&lt;/h4&gt;

&lt;p&gt;For a small number of concurrent tasks, the execution relationship is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python Thread
      ↓
OS Thread
      ↓
Operating System Scheduler
      ↓
CPU Core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OS scheduler decides which OS thread receives CPU time.&lt;/p&gt;

&lt;h4&gt;
  
  
  3.1.2 The Problem: Sharing Interpreter State Across OS Threads
&lt;/h4&gt;

&lt;p&gt;For example, the OS might schedule two Python threads simultaneously on different CPU cores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python Thread A → OS Thread A → CPU Core A
Python Thread B → OS Thread B → CPU Core B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operating system can schedule both threads in parallel. Inside CPython, however, the threads share interpreter state, including object internals, reference counts, and runtime data structures.&lt;/p&gt;

&lt;p&gt;This raises a separate runtime question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How can multiple OS threads safely access shared state while executing Python interpreter code?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The default GIL-enabled CPython build uses the GIL to protect shared interpreter state. Whether a thread can execute protected Python code therefore depends on two independent conditions: OS scheduling and the GIL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OS Scheduler
Decides which OS Thread gets CPU time

GIL
Restricts which Thread can execute protected Python interpreter code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.1.3 The Default Solution: The GIL Restricts Interpreter Execution
&lt;/h4&gt;

&lt;p&gt;In a default GIL-enabled build, a thread must hold the GIL while executing interpreter code protected by it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OS Thread
    ↓
OS Scheduler
    ↓
CPU Time

Python Thread
    ↓
Acquire GIL
    ↓
Python Interpreter Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executing Python bytecode therefore requires two things: the OS thread must have CPU time, and the thread must hold the GIL.&lt;/p&gt;

&lt;p&gt;If another thread already holds the GIL:&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
  ↓
Holds the GIL
  ↓
Executes Python bytecode

Thread B
  ↓
Attempts to acquire the GIL
  ↓
Waits
  ↓
The GIL is released
  ↓
Competes to acquire it again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Their responsibilities remain distinct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The OS scheduler governs &lt;strong&gt;OS Thread → CPU&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The GIL gates &lt;strong&gt;Thread → Python Interpreter Execution&lt;/strong&gt; in a default CPython build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3.1.4 The Next Step: Free-Threaded CPython
&lt;/h4&gt;

&lt;p&gt;To allow multiple Python threads to execute Python code in parallel, the normal execution path must no longer depend on that global lock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python Thread A → OS Thread A → CPU Core A
Python Thread B → OS Thread B → CPU Core B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A free-threaded build does not rely on the GIL as a global execution lock on its normal path. Instead, finer-grained synchronization protects CPython's internal state, allowing multiple Python threads to execute code in parallel. What changes is interpreter synchronization; the OS scheduler still schedules the same kind of native threads.&lt;/p&gt;

&lt;p&gt;CPython thus offers two principal execution models:&lt;/p&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%2Ffaud2dnsumt11x2kp9qj.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%2Ffaud2dnsumt11x2kp9qj.png" alt="Both Python thread models map to native OS threads scheduled by the OS. The GIL restricts interpreter execution in the default build; finer-grained runtime synchronization permits parallel execution in the free-threaded build. Dashed arrows denote interpreter constraints rather than OS scheduling calls." width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some incompatible extensions may still cause a free-threaded runtime to enable the GIL.&lt;/p&gt;

&lt;h4&gt;
  
  
  3.1.5 The Final Model: Thread, PyThreadState, and GIL
&lt;/h4&gt;

&lt;h5&gt;
  
  
  3.1.5.1 Python Thread
&lt;/h5&gt;

&lt;p&gt;Python programs create and manage threads through &lt;code&gt;threading.Thread&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In CPython, &lt;code&gt;_thread&lt;/code&gt; ultimately creates a real OS thread.&lt;/p&gt;

&lt;h5&gt;
  
  
  3.1.5.2 OS Thread
&lt;/h5&gt;

&lt;p&gt;The OS scheduler determines when an OS thread runs on a CPU.&lt;/p&gt;

&lt;p&gt;Unlike Go's G-M-P or Java virtual threads, CPython does not multiplex large numbers of &lt;code&gt;threading.Thread&lt;/code&gt; objects onto a smaller pool of OS threads.&lt;/p&gt;

&lt;h5&gt;
  
  
  3.1.5.3 PyThreadState
&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;PyThreadState&lt;/strong&gt; stores the interpreter-side runtime state associated with a Python thread.&lt;/p&gt;

&lt;p&gt;A thread's execution context in CPython consists of two parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OS Thread
    +
PyThreadState
    ↓
Execution context for this Thread inside the CPython Runtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  3.1.5.4 GIL
&lt;/h5&gt;

&lt;p&gt;In the default GIL-enabled build, a thread must hold the &lt;strong&gt;GIL&lt;/strong&gt; to execute protected Python interpreter code.&lt;/p&gt;

&lt;p&gt;Even when its OS thread has CPU time, it must still satisfy the GIL requirement before executing the corresponding Python bytecode.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Execution Layers
&lt;/h3&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%2F9ofsjzpjpg6er83ons68.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%2F9ofsjzpjpg6er83ons68.png" alt="threading.Thread belongs to the language layer. The CPython runtime manages _thread, PyThreadState, and interpreter execution. In default GIL-enabled builds, the GIL restricts interpreter execution only. The native OS thread is still scheduled onto a CPU by the OS scheduler." width="800" height="728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 From CPython Thread Lifecycles to Their Implementation
&lt;/h3&gt;

&lt;p&gt;CPython relies on OS scheduling for &lt;code&gt;threading.Thread&lt;/code&gt;. The default GIL-enabled build also requires GIL ownership to execute protected interpreter code; free-threaded builds use different synchronization mechanisms.&lt;/p&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%2Fbtijpd23vy60at4hdj3b.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%2Fbtijpd23vy60at4hdj3b.png" alt="The OS schedules native threads, which may block and later resume. GIL ownership is a separate interpreter execution constraint, not a second OS scheduling layer." width="800" height="1039"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3.3.1 Creation and Start: Created → Runnable
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;Thread.start()&lt;/code&gt; creates an OS thread and its &lt;code&gt;PyThreadState&lt;/code&gt;; CPU execution still depends on OS scheduling.&lt;/p&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%2F421ynw8y1lf6vsjrxjyi.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%2F421ynw8y1lf6vsjrxjyi.png" alt=" raw `Thread.start()` endraw  creates an OS thread and its  raw `PyThreadState` endraw ; CPU execution still depends on OS scheduling." width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3.3.2 Waiting for Scheduling: Runnable
&lt;/h4&gt;

&lt;p&gt;The OS thread underlying a Python thread waits for OS scheduling; the default build must also satisfy its GIL requirement.&lt;/p&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%2Fn23vwqriudadou04q854.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%2Fn23vwqriudadou04q854.png" alt="The OS thread underlying a Python thread waits for OS scheduling; the default build must also satisfy its GIL requirement." width="800" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3.3.3 Execution: Runnable → Running
&lt;/h4&gt;

&lt;p&gt;The OS scheduler grants CPU time to the native thread; in the default GIL-enabled build, the thread acquires the GIL before executing Python code.&lt;/p&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%2Foe4o7difzf6n1h4qm2lx.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%2Foe4o7difzf6n1h4qm2lx.png" alt="The OS scheduler grants CPU time to the native thread; in the default GIL-enabled build, the thread acquires the GIL before executing Python code." width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3.3.4 Parking and Waiting: Running → Waiting
&lt;/h4&gt;

&lt;p&gt;Waiting for I/O, locks, or conditions may block the underlying OS thread. In the default build, operations that release the GIL also allow another thread to execute protected interpreter code.&lt;/p&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%2F8267h9dno0ytq7p1qhhn.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%2F8267h9dno0ytq7p1qhhn.png" alt="Blocking I/O, locks, and conditions may park the OS thread; paths that release the GIL also yield interpreter execution in the default build." width="799" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3.3.5 Wakeup and Resumption: Waiting → Runnable → Running
&lt;/h4&gt;

&lt;p&gt;When ready, the OS thread reenters OS scheduling; the default build must reacquire the GIL before resuming protected Python execution if it was released.&lt;/p&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%2F52hvlv2hkhg3xay60jnh.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%2F52hvlv2hkhg3xay60jnh.png" alt="When ready, the OS thread reenters OS scheduling; the default build must reacquire the GIL before resuming protected Python execution if it was released." width="799" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3.3.6 Completion and Cleanup: Running → Terminated
&lt;/h4&gt;

&lt;p&gt;After &lt;code&gt;Thread.run()&lt;/code&gt; returns, the runtime cleans up &lt;code&gt;PyThreadState&lt;/code&gt;, the native thread exits, and its ThreadHandle reaches the completed state.&lt;/p&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%2F9mebgsrmhbiigkz2mp0x.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%2F9mebgsrmhbiigkz2mp0x.png" alt="After  raw `Thread.run()` endraw  returns, the runtime cleans up  raw `PyThreadState` endraw , the native thread exits, and ThreadHandle completes." width="798" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 End-to-End Sequence Diagram
&lt;/h3&gt;

&lt;p&gt;Here is the thread lifecycle in default GIL-enabled CPython:&lt;/p&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%2Facrcb3r24nhcsfcisn4m.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%2Facrcb3r24nhcsfcisn4m.png" alt="The OS schedules the native thread; GIL ownership independently constrains interpreter execution in the default build, including waits and resumption." width="800" height="773"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For implementation details, see CPython's &lt;a href="https://github.com/python/cpython/blob/main/Modules/_threadmodule.c" rel="noopener noreferrer"&gt;&lt;code&gt;_threadmodule.c&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/python/cpython/blob/main/Python/pystate.c" rel="noopener noreferrer"&gt;&lt;code&gt;pystate.c&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/python/cpython/blob/main/Python/ceval_gil.c" rel="noopener noreferrer"&gt;&lt;code&gt;ceval_gil.c&lt;/code&gt;&lt;/a&gt;; for free-threading, see the &lt;a href="https://docs.python.org/3/howto/free-threading-python.html" rel="noopener noreferrer"&gt;official guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.5 Returning to the Opening Question
&lt;/h3&gt;

&lt;p&gt;Section 3.1.5, Final Model and Section 3.2, Execution Layers show that &lt;code&gt;threading.Thread&lt;/code&gt; maps to a native OS thread, while &lt;code&gt;PyThreadState&lt;/code&gt; records its interpreter state. &lt;strong&gt;Therefore, the OS scheduler directly grants CPU execution opportunities; CPython has no separate user-space thread scheduler like Go's G-M-P.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Section 3.1.3, GIL, Section 3.3.2, Waiting for Scheduling, and Section 3.3.3, Execution show that a thread in the default GIL-enabled build must acquire the GIL before executing protected Python interpreter code. &lt;strong&gt;Therefore, CPU time alone does not guarantee Python-code execution; the GIL is an execution constraint, not another thread scheduler.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Section 3.3.4, Parking and Waiting, Section 3.3.5, Resumption, and Section 3.1.4, Free-Threaded establish that a blocking operation can suspend an OS thread and release the GIL, whereas free-threaded builds change Python-code synchronization without changing the &lt;code&gt;threading.Thread&lt;/code&gt; to OS-thread mapping. &lt;strong&gt;Therefore, the interpreter's concurrency constraints change, but the execution carrier does not.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answering the opening question:&lt;/strong&gt; CPython relies on the OS scheduler for native threads. The default build uses the GIL to limit parallel execution of Python code within one interpreter, whereas free-threaded builds permit multiple threads to execute Python code in parallel; neither model multiplexes large numbers of &lt;code&gt;threading.Thread&lt;/code&gt; instances onto a few OS threads.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. What the Three Implementations Have in Common
&lt;/h2&gt;

&lt;p&gt;After examining Java, Go, and CPython, we can set aside their specific APIs and runtime names. Three questions remain: &lt;strong&gt;what carries an execution unit, who decides when it runs, and how does it resume after waiting?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 How Does an Execution Unit Reach the CPU? — Establish Its Carrier
&lt;/h3&gt;

&lt;p&gt;First, distinguish the &lt;strong&gt;execution unit&lt;/strong&gt; defined by a language or runtime from the &lt;strong&gt;OS thread&lt;/strong&gt; that the operating system schedules. They do not necessarily have a one-to-one relationship.&lt;/p&gt;

&lt;p&gt;Suppose a program has three execution units, A, B, and C. The most direct approach gives each its own OS thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Execution Unit A → OS Thread 1
Execution Unit B → OS Thread 2
Execution Unit C → OS Thread 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tasks may, however, spend most of their time waiting for I/O. If every task permanently occupies an OS thread, native-thread resources must remain allocated even for waiting tasks.&lt;/p&gt;

&lt;p&gt;Another approach separates &lt;strong&gt;tasks&lt;/strong&gt; from &lt;strong&gt;underlying threads&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Runnable Task A ─┐
Runnable Task B ─┼─→ Runtime Dispatch → OS Thread 1
Runnable Task C ─┘                 (reused over time)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One OS thread can execute different tasks at different times. Models that do not require user-space scheduling can keep the direct one-to-one mapping. Both ultimately reach the same execution path:&lt;/p&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%2Fm566ny3uk18r77r7upwx.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%2Fm566ny3uk18r77r7upwx.png" alt="Execution units reach OS threads through direct mapping or runtime carriers; the OS scheduler schedules native threads onto the CPU." width="552" height="1004"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Therefore, execution units can have different carriers, but the OS scheduler ultimately dispatches OS threads—not language-level tasks—to the CPU.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Who Selects What Runs Next? — Separate the Scheduling Layers
&lt;/h3&gt;

&lt;p&gt;Establishing a carrier does not mean that a task immediately runs. Two different decisions remain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task selection:&lt;/strong&gt; If a runtime manages lightweight runnable tasks, it chooses one to assign to an underlying thread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thread scheduling:&lt;/strong&gt; The OS scheduler selects a runnable OS thread to receive CPU time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider a single CPU core. OS threads T1 and T2 are both runnable, and a runtime has already assigned task A to T1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Runtime: select Task A → assign to T1

OS Scheduler: T1 and T2 are both Runnable
                         ↓
                    select T2 first
                         ↓
                   CPU runs T2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although the runtime has selected task A, &lt;strong&gt;it cannot make progress until T1 receives CPU time&lt;/strong&gt;. Conversely, an OS thread receiving CPU time does not guarantee that a language-level synchronization condition is satisfied; such a condition is not another thread scheduler.&lt;/p&gt;

&lt;p&gt;With direct one-to-one mapping, a separate runtime thread scheduler may be unnecessary, but OS scheduling still applies. Long-running work may also have to yield or be preempted so other tasks can make progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Therefore, runtime task dispatch and OS thread scheduling make choices at different levels; Runnable is not the same as Running.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 What Happens After a Wait? — Regain an Execution Opportunity
&lt;/h3&gt;

&lt;p&gt;Suppose task A is executing and then starts an I/O operation, while task B is already runnable. The central question is: &lt;strong&gt;can A's underlying OS thread run B while A waits?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task A running → waits for I/O
                     │
                     ├─ Suspend only logical Task A
                     │       └─ OS thread can run Task B
                     │
                     └─ Block the underlying OS thread
                             └─ That thread waits; other OS threads can run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Either path is possible, depending on the execution model and whether the specific wait releases its carrier. &lt;strong&gt;Suspending a logical task does not necessarily block its OS thread; blocking one OS thread does not stop the entire process.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the I/O completes, task A is not guaranteed to run immediately. It becomes eligible for scheduling again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Waiting
   ↓ condition ready / wakeup
Runnable
   ↓ optionally queue in runtime and wait for a carrier
   ↓ OS thread receives CPU time
Running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If only the logical task was suspended, the OS thread could make progress on other tasks. If the OS thread itself was blocked, it must become runnable and receive CPU time again. &lt;strong&gt;Waiting and resumption determine whether execution resources can be reused; wakeup does not mean immediate execution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Returning to the opening question: &lt;strong&gt;a language-level execution unit must first be associated with an OS thread. When present, a runtime scheduler selects the task; the OS scheduler assigns CPU time to its underlying thread. Waiting and resumption determine whether that thread can serve other tasks in the meantime. Together, these mechanisms explain how concurrent execution units reach the CPU.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Next: Language Memory Models
&lt;/h2&gt;

&lt;p&gt;The previous chapter explained how the CPU executes instructions. This chapter addresses the missing link: how runtimes and operating systems give concurrent execution units CPU time and resume them after waiting.&lt;/p&gt;

&lt;p&gt;With the execution path established, the next chapter turns to the &lt;strong&gt;Language Memory Model&lt;/strong&gt;—the rules governing concurrent reads and writes, which provide the foundation for later discussions of Mutex, Atomic, Volatile, and other synchronization mechanisms.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.com/en/articles/concurrency-series-01-5-thread-scheduling/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>java</category>
      <category>python</category>
      <category>go</category>
      <category>programming</category>
    </item>
    <item>
      <title>Concurrency Programming (8): Read-Write Locks — From Language Rules to the CPU</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Wed, 07 Oct 2026 11:16:25 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/concurrency-programming-8-read-write-locks-from-language-rules-to-the-cpu-2e2f</link>
      <guid>https://dev.to/thinkerqaq/concurrency-programming-8-read-write-locks-from-language-rules-to-the-cpu-2e2f</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;0. What Does This Article Answer Next?&lt;/li&gt;
&lt;li&gt;
1. What Does Java ReadWriteLock Guarantee at the Language Level?

&lt;ul&gt;
&lt;li&gt;1.1 Atomicity&lt;/li&gt;
&lt;li&gt;1.2 Visibility and Ordering&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
2. How Does the JDK Implement ReentrantReadWriteLock?

&lt;ul&gt;
&lt;li&gt;2.1 Layers&lt;/li&gt;
&lt;li&gt;2.2 End-to-End Path from Write Lock to Read Lock&lt;/li&gt;
&lt;li&gt;2.3 Guarantees at the Runtime / Language Implementation Layer&lt;/li&gt;
&lt;li&gt;2.3.1 Atomicity&lt;/li&gt;
&lt;li&gt;2.3.2 Visibility and Ordering&lt;/li&gt;
&lt;li&gt;2.4 Guarantees at the Hardware Layer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
3. How Does the Go Runtime Implement sync.RWMutex?

&lt;ul&gt;
&lt;li&gt;3.1 Layers&lt;/li&gt;
&lt;li&gt;3.2 End-to-End Path from Write Lock to Read Lock&lt;/li&gt;
&lt;li&gt;3.3 Guarantees at the Runtime / Language Implementation Layer&lt;/li&gt;
&lt;li&gt;3.3.1 Atomicity&lt;/li&gt;
&lt;li&gt;3.3.2 Visibility and Ordering&lt;/li&gt;
&lt;li&gt;3.4 Guarantees at the Hardware Layer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4. CPython: Why Is There No Read-Write Lock in the Standard Library?&lt;/li&gt;
&lt;li&gt;
5. Common Pattern Across the Two Implementations

&lt;ul&gt;
&lt;li&gt;5.1 Who Gets In? — Atomically Maintaining Reader / Writer State&lt;/li&gt;
&lt;li&gt;5.2 What Happens on Conflict? — Park / Wakeup&lt;/li&gt;
&lt;li&gt;5.3 Why Can a Later Reader See an Earlier Writer's Data?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;6. How ReadWriteLock and Mutex Relate&lt;/li&gt;
&lt;li&gt;7. Next: AQS / AQLS — Java's Unified Synchronizer Framework&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  0. What Does This Article Answer Next?
&lt;/h2&gt;

&lt;p&gt;The previous article ended by returning to Mutex: it protects an entire critical section, but it allows only one execution unit to enter at a time, regardless of whether that unit reads or writes.&lt;/p&gt;

&lt;p&gt;If two threads only read shared state, they do not need to block each other:&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                         Thread B

read counter                    read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A read-write lock refines Mutex by distinguishing &lt;strong&gt;Readers&lt;/strong&gt; from &lt;strong&gt;Writers&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read  + Read   → concurrent
Read  + Write  → exclusive
Write + Write  → exclusive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This article continues with the same two examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;counter++&lt;/code&gt; to see how the Write Lock provides Atomicity;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;counter + ready&lt;/code&gt; to see how Write Lock → Read Lock establishes Visibility and Ordering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As before, we start with the language-level rules, then trace how the JDK, HotSpot, and the CPU implement them.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What Does Java ReadWriteLock Guarantee at the Language Level?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Atomicity
&lt;/h3&gt;

&lt;p&gt;Java's &lt;a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/locks/ReadWriteLock.html" rel="noopener noreferrer"&gt;&lt;code&gt;ReadWriteLock&lt;/code&gt;&lt;/a&gt; defines the Reader / Writer boundary directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“The read lock may be held simultaneously by multiple reader threads, so long as there are no writers.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It also states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“The write lock is exclusive.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reads may therefore proceed concurrently, while writes remain exclusive.&lt;/p&gt;

&lt;p&gt;Use the same &lt;code&gt;counter++&lt;/code&gt; example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ReentrantReadWriteLock&lt;/span&gt; &lt;span class="n"&gt;rw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReentrantReadWriteLock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Lock&lt;/span&gt; &lt;span class="n"&gt;writeLock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeLock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;writeLock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;writeLock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unlock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With two threads updating concurrently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial: counter = 0

Thread A                         Thread B

writeLock.lock()
                                 writeLock.lock()
                                 wait
read counter -&amp;gt; 0
counter + 1 -&amp;gt; 1
write counter -&amp;gt; 1
writeLock.unlock()
                                 writeLock.lock() returns
                                 read counter -&amp;gt; 1
                                 counter + 1 -&amp;gt; 2
                                 write counter -&amp;gt; 2
                                 writeLock.unlock()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final value is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As with Mutex, Atomicity comes from &lt;strong&gt;serializing conflicting critical sections under the Write Lock&lt;/strong&gt;, not from turning &lt;code&gt;counter++&lt;/code&gt; itself into an atomic instruction.&lt;/p&gt;

&lt;p&gt;A Read Lock should protect reads only. Multiple Readers may enter together, so it cannot be used to protect shared writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Visibility and Ordering
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ReadWriteLock&lt;/code&gt; also defines memory synchronization between the Write Lock and the associated Read Lock:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“A thread successfully acquiring the read lock will see all updates made upon previous release of the write lock.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use the same &lt;code&gt;counter / ready&lt;/code&gt; example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;ReentrantReadWriteLock&lt;/span&gt; &lt;span class="n"&gt;rw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReentrantReadWriteLock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Lock&lt;/span&gt; &lt;span class="n"&gt;readLock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readLock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Lock&lt;/span&gt; &lt;span class="n"&gt;writeLock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeLock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Thread A&lt;/span&gt;
&lt;span class="n"&gt;writeLock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;writeLock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unlock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Thread B&lt;/span&gt;
&lt;span class="n"&gt;readLock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;readLock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unlock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If B successfully acquires the Read Lock after A releases the Write Lock:&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                              Thread B

counter = 1
    │
    │ program order
    ▼
ready = true
    │
    ▼
writeLock.unlock()
    │
    │ memory synchronization
    ▼
                                    readLock.lock() returns
                                        │
                                        │ program order
                                        ▼
                                    read ready == true
                                    read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By happens-before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;write counter
    ↓ happens-before
write ready
    ↓
writeLock.unlock
    ↓ memory synchronization
readLock.lock returns
    ↓ happens-before
read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So once B has acquired the subsequent Read Lock and observes &lt;code&gt;ready == true&lt;/code&gt;, the earlier &lt;code&gt;counter = 1&lt;/code&gt; must be visible. This outcome is not allowed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ready == true
counter == 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives us two guarantees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visibility&lt;/strong&gt;: writes completed by the Writer before releasing the Write Lock are visible to a later Reader that acquires the Read Lock;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordering&lt;/strong&gt;: the Writer's writes, Write Lock release, Read Lock acquisition, and the Reader's reads form a happens-before order.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. How Does the JDK Implement ReentrantReadWriteLock?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 Layers
&lt;/h3&gt;

&lt;p&gt;Start with the implementation stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java Source Code
Example: readLock.lock() / writeLock.lock()
Role: declare read and write critical sections
        │
        ▼
JVM Bytecode
Example: invokeinterface Lock.lock / Lock.unlock
Role: invoke the ReadLock / WriteLock API
        │
        ▼
Runtime / Language Implementation
Example: ReentrantReadWriteLock.Sync / AbstractQueuedLongSynchronizer
       + Unsafe CAS / LockSupport
Role: implement Reader / Writer state, contention, queuing, and wake-up
        │
        ▼
OS Thread
Example: Runnable / Parked Java Thread
Role: block on contention and become runnable again after unpark
        │
        ▼
x86-64 Hardware
Example: Atomic Instruction / Cache Coherence / Memory Ordering
Role: provide the hardware basis for Atomicity, Visibility, and Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 End-to-End Path from Write Lock to Read Lock
&lt;/h3&gt;

&lt;p&gt;Continue with &lt;code&gt;counter / ready&lt;/code&gt;: Thread A writes under the Write Lock, then Thread B reads under the Read Lock.&lt;/p&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%2Fxt6efq6w1ndl5l4c19qt.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%2Fxt6efq6w1ndl5l4c19qt.png" alt="How a Writer acquires and releases the Write Lock, how a Reader acquires the Read Lock, and how a contended Java Thread enters the park / wake-up path." width="800" height="2002"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sequence diagram keeps only the cross-layer path. The Runtime / Language Implementation layer can be expanded into the Reader / Writer contention flow below:&lt;/p&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%2Fb55g1i2ayhgp1h9nmwtb.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%2Fb55g1i2ayhgp1h9nmwtb.png" alt="Read Lock and Write Lock use Shared and Exclusive modes. On conflict the thread enters the AQLS synchronization queue, parks, wakes, and retries." width="800" height="1633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two counters are encoded in the synchronization state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;high 32 bits
  → shared count
  → number of Readers

low 32 bits
  → exclusive count
  → Writer reentrancy count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In JDK 25, &lt;code&gt;ReentrantReadWriteLock.Sync&lt;/code&gt; extends &lt;code&gt;AbstractQueuedLongSynchronizer&lt;/code&gt;. Readers use Shared mode; Writers use Exclusive mode; both ultimately contend on the same 64-bit &lt;code&gt;state&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;See OpenJDK 25 &lt;a href="https://github.com/openjdk/jdk/blob/jdk-25%2B36/src/java.base/share/classes/java/util/concurrent/locks/ReentrantReadWriteLock.java" rel="noopener noreferrer"&gt;&lt;code&gt;ReentrantReadWriteLock.java&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/openjdk/jdk/blob/jdk-25%2B36/src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java" rel="noopener noreferrer"&gt;&lt;code&gt;AbstractQueuedLongSynchronizer.java&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Guarantees at the Runtime / Language Implementation Layer
&lt;/h3&gt;

&lt;h4&gt;
  
  
  2.3.1 Atomicity
&lt;/h4&gt;

&lt;p&gt;Atomicity comes from the state transitions in the flowchart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Writer
  → CAS exclusive count
  → exclusive write critical section after success

Reader
  → CAS shared count
  → multiple Readers may succeed independently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Writer fails if Readers are present or another thread owns the Write Lock.&lt;/p&gt;

&lt;p&gt;If the current thread already owns the Write Lock, acquisition is reentrant and increments the exclusive count.&lt;/p&gt;

&lt;p&gt;A Reader fails if another thread owns the Write Lock; otherwise it increments the shared count with CAS.&lt;/p&gt;

&lt;p&gt;The runtime therefore enforces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read + Read
  → multiple Shared acquires allowed

Read + Write
  → conflict

Write + Write
  → conflict
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2.3.2 Visibility and Ordering
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;AbstractQueuedLongSynchronizer&lt;/code&gt; stores its state in a &lt;code&gt;volatile long&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its basic operations have these semantics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getState()
  → volatile read

setState()
  → volatile write

compareAndSetState()
  → volatile read + volatile write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Releasing the Write Lock updates &lt;code&gt;state&lt;/code&gt;. A later Reader reads and CASes the same &lt;code&gt;state&lt;/code&gt; while acquiring the Read Lock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Writer writes
    ↓
release Write Lock
    ↓
volatile / CAS state synchronization
    ↓
acquire Read Lock
    ↓
Reader reads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the language-level Visibility and Ordering guarantees are carried by the Runtime / Language Implementation layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 Guarantees at the Hardware Layer
&lt;/h3&gt;

&lt;p&gt;At the hardware layer, the implementation reduces to the same three capabilities used throughout the series:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomicity
  → Atomic Instruction
  → x86-64: LOCKed CAS and other atomic RMW operations

Visibility
  → Cache Coherence
  → other CPU cores cannot keep using an invalidated old cache line indefinitely

Ordering
  → x86-64 Memory Ordering + LOCKed RMW / required ordering constraints
  → preserve the memory-access order required around lock-state transitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;compareAndSetState()&lt;/code&gt; ultimately uses &lt;code&gt;Unsafe.compareAndSetLong&lt;/code&gt;, which HotSpot lowers to an atomic CAS on the target architecture.&lt;/p&gt;

&lt;p&gt;Read-write locks add no new hardware primitive. They build the rule “multiple Readers or one Writer” on top of atomic state plus thread waiting / wake-up.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. How Does the Go Runtime Implement sync.RWMutex?
&lt;/h2&gt;

&lt;p&gt;Go's standard library directly provides &lt;a href="https://pkg.go.dev/sync#RWMutex" rel="noopener noreferrer"&gt;&lt;code&gt;sync.RWMutex&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It shares the same core Reader / Writer exclusion model as Java &lt;code&gt;ReadWriteLock&lt;/code&gt;: multiple Readers may proceed concurrently, while a Writer must be exclusive. The complete API semantics are not identical.&lt;/p&gt;

&lt;p&gt;The most important differences are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Java &lt;code&gt;ReentrantReadWriteLock&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Go &lt;code&gt;sync.RWMutex&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read-lock reentrancy&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Recursive &lt;code&gt;RLock&lt;/code&gt; is not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write-lock reentrancy&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write → Read downgrade&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read → Write upgrade&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fairness&lt;/td&gt;
&lt;td&gt;Optional fair / nonfair mode&lt;/td&gt;
&lt;td&gt;No fair mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Go Memory Model states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“For any call to RLock, there exists an n such that the n'th call to Unlock ‘synchronizes before’ that call to RLock.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3.1 Layers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go Source Code
Example: rw.RLock() / rw.Lock()
Role: declare read and write critical sections
        │
        ▼
Go Implementation
Example: sync.RWMutex / sync.Mutex / sync/atomic / Runtime Semaphore
Role: implement Reader / Writer state, contention, waiting, and wake-up
        │
        ▼
Goroutine / Scheduler
Example: Runnable / Parked Goroutine
Role: park a contending Goroutine and schedule it again after wake-up
        │
        ▼
x86-64 Hardware
Example: Atomic Instruction / Cache Coherence / Memory Ordering
Role: provide the hardware basis for Atomicity, Visibility, and Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important distinction from Java is that &lt;code&gt;runtime_SemacquireRWMutex*&lt;/code&gt; parks a Goroutine rather than directly blocking one fixed OS thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 End-to-End Path from Write Lock to Read Lock
&lt;/h3&gt;

&lt;p&gt;Continue with the same &lt;code&gt;counter / ready&lt;/code&gt; example:&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;var&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rw&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Goroutine A writes first, then Goroutine B reads:&lt;/p&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%2Fgmi8ygxgdxynn3jpm9os.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%2Fgmi8ygxgdxynn3jpm9os.png" alt="How a Writer acquires and releases the Write Lock, how a Reader acquires the Read Lock, and how a contending Goroutine enters the park / wake-up path." width="800" height="2057"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sequence diagram keeps only the cross-layer path. The Go Implementation layer can be expanded into the Reader / Writer contention flow below:&lt;/p&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%2Fs0cbhpklq0qjiipb5yte.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%2Fs0cbhpklq0qjiipb5yte.png" alt="Readers enter through readerCount. A Writer first serializes against other Writers, announces a pending Writer, and then waits for active Readers to leave." width="800" height="1735"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core state is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;w
  → serialize Writers

readerCount
  → &amp;gt;= 0: number of active Readers
  → &amp;lt; 0: a Writer is pending

readerWait
  → number of departing Readers the Writer is still waiting for

readerSem / writerSem
  → Reader / Writer waiting and wake-up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See Go's &lt;a href="https://github.com/golang/go/blob/master/src/sync/rwmutex.go" rel="noopener noreferrer"&gt;&lt;code&gt;sync/rwmutex.go&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Guarantees at the Runtime / Language Implementation Layer
&lt;/h3&gt;

&lt;h4&gt;
  
  
  3.3.1 Atomicity
&lt;/h4&gt;

&lt;p&gt;Atomicity comes from two core state operations in the flowchart.&lt;/p&gt;

&lt;p&gt;A Reader enters through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;readerCount.Add(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That update must be atomic. Multiple Readers may increment the count independently.&lt;/p&gt;

&lt;p&gt;A Writer first serializes against other Writers through the internal &lt;code&gt;w Mutex&lt;/code&gt;, then executes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;readerCount.Add(-rwmutexMaxReaders)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to mark a pending Writer.&lt;/p&gt;

&lt;p&gt;The runtime therefore enforces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read + Read
  → multiple Readers allowed

Read + Write
  → new Readers block once a Writer is pending

Write + Write
  → serialized by w Mutex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike Java, Go does not encode every state in one &lt;code&gt;state&lt;/code&gt; word. It combines a &lt;code&gt;Mutex + Atomic Counter + Semaphore&lt;/code&gt; to implement the same Reader / Writer rules.&lt;/p&gt;

&lt;h4&gt;
  
  
  3.3.2 Visibility and Ordering
&lt;/h4&gt;

&lt;p&gt;Go defines &lt;code&gt;RWMutex&lt;/code&gt; synchronization at the API level; it should not be inferred from &lt;code&gt;readerCount&lt;/code&gt; atomics alone.&lt;/p&gt;

&lt;p&gt;The documented relations include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unlock
  → synchronized-before
  → the corresponding later RLock

RUnlock
  → synchronized-before
  → the corresponding later Lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the same &lt;code&gt;counter / ready&lt;/code&gt; example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine A                           Goroutine B

counter = 1
ready = true
    │
    ▼
Unlock() ── synchronized-before ──► RLock() returns
                                        │
                                        ▼
                                    read ready == true
                                    read counter == 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So once B acquires the Read Lock through this synchronization edge, A's writes under the Write Lock are visible to B in the required order.&lt;/p&gt;

&lt;p&gt;Internally, the Mutex, runtime semaphores, and atomic state implement this path. The &lt;code&gt;readerCount.Add&lt;/code&gt; operation alone should not be interpreted as synchronizing Readers with one another.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Guarantees at the Hardware Layer
&lt;/h3&gt;

&lt;p&gt;At the hardware layer, the implementation reduces to the same three capabilities used throughout the series:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomicity
  → Atomic Instruction
  → x86-64: LOCK XADDL / LOCK CMPXCHGL and other atomic RMW operations

Visibility
  → Cache Coherence
  → other CPU cores cannot keep using an invalidated old cache line indefinitely

Ordering
  → x86-64 Memory Ordering + LOCKed RMW
  → provide the ordering constraints required by the synchronization path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;readerCount.Add&lt;/code&gt; and &lt;code&gt;readerWait.Add&lt;/code&gt; ultimately use the x86-64 atomic-add path. The internal &lt;code&gt;w Mutex&lt;/code&gt; reuses the CAS / atomic machinery already covered in the Mutex implementation article.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;sync.RWMutex&lt;/code&gt; introduces no new hardware primitive. It combines atomics, a Mutex, and runtime semaphores to implement Reader / Writer semantics.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. CPython: Why Is There No Read-Write Lock in the Standard Library?
&lt;/h2&gt;

&lt;p&gt;Python is capable of implementing a Read-Write Lock. In fact, CPython had an early proposal to add &lt;code&gt;RWLock&lt;/code&gt; to &lt;code&gt;threading&lt;/code&gt;: &lt;a href="https://bugs.python.org/issue8800" rel="noopener noreferrer"&gt;Issue 8800&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The proposal never made it into the standard library. Looking at the long-running Issue 8800 discussion, at least two recurring obstacles stand out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Under traditional CPython, the GIL limited the benefit of a Read-Write Lock&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A CPython developer summarized the problem directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“since the GIL serializes everything anyway, this isn't likely to benefit many situations”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The basic trade-off was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Main benefit of a Read-Write Lock
  → multiple Readers run in parallel

Traditional CPython
  → the GIL largely serializes execution of Python code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So even if multiple threads could acquire the Read Lock together, they usually could not execute Python code in parallel.&lt;/p&gt;

&lt;p&gt;RWLock was not useless: I/O and C extensions that release the GIL could still benefit. But for general CPython threading, its advantage was much less direct than in Java or Go.&lt;/p&gt;

&lt;p&gt;Free-threaded CPython changes this point. Without the GIL, multiple Python threads can truly execute in parallel, so concurrent Readers become substantially more valuable.&lt;/p&gt;

&lt;p&gt;That changes the first part of the trade-off. But even if the benefit of RWLock becomes clearer, the standard library still faces the second problem: &lt;strong&gt;there was no consensus on the API and scheduling semantics.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Issue 8800 did not reach consensus on the API or scheduling policy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The discussion had two main classes of disagreement.&lt;/p&gt;

&lt;p&gt;The first was the &lt;strong&gt;API shape&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;one unified RWLock
or
two associated Shared / Exclusive Lock objects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One proposal argued that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“having two different lock primitives ... would be much more flexible, pythonic”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Others preferred a single &lt;code&gt;RWLock&lt;/code&gt; abstraction.&lt;/p&gt;

&lt;p&gt;The second was the &lt;strong&gt;scheduling policy&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reader priority or Writer priority?
Fair / FIFO?
May new Readers enter while a Writer waits?
Can a Read Lock be upgraded?
Can a Write Lock be downgraded?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those choices directly affect RWLock behavior and API semantics, and Issue 8800 never converged on one standard design.&lt;/p&gt;

&lt;p&gt;So the obstacle was not implementation feasibility. &lt;strong&gt;There was no consensus on which RWLock API and policy the standard library should permanently define.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The current &lt;code&gt;threading&lt;/code&gt; standard library still provides &lt;code&gt;Lock&lt;/code&gt;, &lt;code&gt;RLock&lt;/code&gt;, &lt;code&gt;Condition&lt;/code&gt;, &lt;code&gt;Semaphore&lt;/code&gt;, &lt;code&gt;Event&lt;/code&gt;, and related primitives, but no Read-Write Lock.&lt;/p&gt;

&lt;p&gt;Applications that need RWLock semantics generally have two options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use a third-party / custom RWLock
        │
        └── obtain explicit Read Lock / Write Lock semantics

Build on threading.Lock + Condition
        │
        └── define Reader / Writer counts and scheduling policy yourself
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original Issue 8800 proposal itself implemented RWLock by composing a condition variable and a lock.&lt;/p&gt;

&lt;p&gt;What Python lacks is therefore a &lt;strong&gt;standardized RWLock API and policy in the standard library&lt;/strong&gt;, not the ability to implement Read-Write Lock semantics.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Common Pattern Across the Two Implementations
&lt;/h2&gt;

&lt;p&gt;Strip away the APIs and compare what Java and Go must solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Who Gets In? — Atomically Maintaining Reader / Writer State
&lt;/h3&gt;

&lt;p&gt;A Mutex only needs to answer whether the lock is held.&lt;/p&gt;

&lt;p&gt;A ReadWriteLock must maintain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reader Count
+
Writer State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;64-bit state
  → shared count
  → exclusive count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;readerCount
+
Writer Mutex
+
readerWait
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The encoding differs, but the admission rules are the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reader
  → no conflicting Writer

Writer
  → no other Writer
  → no Reader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those state transitions must be atomic. Otherwise competing execution units could observe stale state and enter incorrectly.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 What Happens on Conflict? — Park / Wakeup
&lt;/h3&gt;

&lt;p&gt;After a state check fails, a waiter cannot spin forever.&lt;/p&gt;

&lt;p&gt;Both implementations reduce to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;check Reader / Writer state
        │
        ├── allowed
        │      └── update state and enter
        │
        └── conflict
               │
               ▼
           queue / wait
               │
               ▼
              Park
               │
               ▼
       relevant holder releases
               │
               ▼
            Wakeup
               │
               └── Retry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parked unit differs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java
  → AQLS Sync Queue
  → LockSupport.park()
  → OS Thread Parked

Go
  → Runtime Semaphore
  → Park Goroutine
  → Scheduler runs another Goroutine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the common pattern is &lt;strong&gt;Conflict → Park → Wakeup → Retry&lt;/strong&gt;. Java parks a Thread; Go parks a Goroutine.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Why Can a Later Reader See an Earlier Writer's Data?
&lt;/h3&gt;

&lt;p&gt;The final problem is the synchronization boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Writer writes
    │
    ▼
Write Unlock [Release]
    │
    ▼
Read Lock [Acquire]
    │
    ▼
Reader reads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java implements this boundary through AQLS volatile / CAS state transitions.&lt;/p&gt;

&lt;p&gt;Go defines the semantics through &lt;code&gt;RWMutex&lt;/code&gt; synchronized-before rules and implements them with its internal Mutex, runtime semaphores, and atomic operations.&lt;/p&gt;

&lt;p&gt;Both implementations must guarantee the same result:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Writes before the Writer releases the lock precede reads performed after the later Reader acquires it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. How ReadWriteLock and Mutex Relate
&lt;/h2&gt;

&lt;p&gt;Both protect critical sections. The difference is whether Readers and Writers are distinguished:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Mutex&lt;/th&gt;
&lt;th&gt;ReadWriteLock&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core problem&lt;/td&gt;
&lt;td&gt;mutual exclusion for a critical section&lt;/td&gt;
&lt;td&gt;distinguish read and write critical sections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Atomicity&lt;/td&gt;
&lt;td&gt;entire critical section is exclusive&lt;/td&gt;
&lt;td&gt;conflicting operations are exclusive; Readers may coexist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visibility&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordering&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read–Read&lt;/td&gt;
&lt;td&gt;Exclusive&lt;/td&gt;
&lt;td&gt;Concurrent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read–Write&lt;/td&gt;
&lt;td&gt;Exclusive&lt;/td&gt;
&lt;td&gt;Exclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write–Write&lt;/td&gt;
&lt;td&gt;Exclusive&lt;/td&gt;
&lt;td&gt;Exclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use&lt;/td&gt;
&lt;td&gt;general shared-state protection&lt;/td&gt;
&lt;td&gt;read-heavy workloads with real contention in read-side critical sections&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A compact model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mutex
  → everyone uses one Exclusive entry

ReadWriteLock
  → Readers use the Shared entry
  → Writers use the Exclusive entry
  → only Read + Read is relaxed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ReadWriteLock is not a “stronger” Mutex.&lt;/p&gt;

&lt;p&gt;It refines the exclusion boundary so non-conflicting Readers may proceed concurrently.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Next: AQS / AQLS — Java's Unified Synchronizer Framework
&lt;/h2&gt;

&lt;p&gt;This article has already crossed the same path several times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ReentrantReadWriteLock
        ↓
Sync
        ↓
AbstractQueuedLongSynchronizer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not merely an implementation detail of one lock. It belongs to the lower-level synchronizer framework used throughout Java's concurrency library.&lt;/p&gt;

&lt;p&gt;The next article goes deeper into the AQS family:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AQS / AQLS
  → state
  → Exclusive / Shared
  → CLH-style Queue
  → acquire / release
  → park / unpark
  → Condition Queue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In JDK 25, &lt;code&gt;ReentrantReadWriteLock&lt;/code&gt; specifically uses &lt;code&gt;AbstractQueuedLongSynchronizer&lt;/code&gt;. Its core model is the same as &lt;code&gt;AbstractQueuedSynchronizer&lt;/code&gt;, but the synchronization state is a &lt;code&gt;long&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Go has no single framework directly equivalent to AQS.&lt;/p&gt;

&lt;p&gt;Instead, Go synchronization primitives tend to compose lower-level mechanisms directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomic State
+
Mutex
+
Runtime Semaphore
+
Goroutine Scheduler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, &lt;code&gt;sync.RWMutex&lt;/code&gt; directly combines &lt;code&gt;w&lt;/code&gt;, &lt;code&gt;readerCount&lt;/code&gt;, &lt;code&gt;readerWait&lt;/code&gt;, &lt;code&gt;readerSem&lt;/code&gt;, and &lt;code&gt;writerSem&lt;/code&gt; rather than inheriting from a generic AQS-style synchronizer.&lt;/p&gt;

&lt;p&gt;AQS / AQLS is therefore a major layer in Java's lock implementation stack and deserves its own article next.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.github.io/en/articles/concurrency-series-08-read-write-lock/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>backend</category>
      <category>computerscience</category>
      <category>java</category>
      <category>programming</category>
    </item>
    <item>
      <title>Concurrency Programming (7): volatile — From Language Semantics to the CPU</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Sat, 03 Oct 2026 12:24:28 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/concurrency-programming-7-volatile-from-language-semantics-to-the-cpu-4jmo</link>
      <guid>https://dev.to/thinkerqaq/concurrency-programming-7-volatile-from-language-semantics-to-the-cpu-4jmo</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;0. What Does This Article Answer Next?&lt;/li&gt;
&lt;li&gt;
1. What Guarantees Does Java volatile Provide at the Language Level?

&lt;ul&gt;
&lt;li&gt;1.1 Visibility and Ordering&lt;/li&gt;
&lt;li&gt;1.2 volatile Does Not Make Compound Operations Atomic&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
2. How Does HotSpot Implement volatile?

&lt;ul&gt;
&lt;li&gt;2.1 Layers&lt;/li&gt;
&lt;li&gt;2.2 End-to-End Implementation Path for a volatile Write and Read&lt;/li&gt;
&lt;li&gt;2.3 Guarantees at the Runtime / Language Implementation Layer&lt;/li&gt;
&lt;li&gt;2.3.1 Visibility&lt;/li&gt;
&lt;li&gt;2.3.2 Ordering&lt;/li&gt;
&lt;li&gt;2.4 Guarantees at the Hardware Layer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
3. Why Don't Go and CPython Have Java-style volatile?

&lt;ul&gt;
&lt;li&gt;3.1 Go: A Language Design Choice&lt;/li&gt;
&lt;li&gt;3.2 CPython: Synchronization Remains a Runtime Responsibility&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4. How volatile, Atomics, and Mutexes Differ&lt;/li&gt;
&lt;li&gt;5. Next: From Mutexes to Read-Write Locks&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  0. What Does This Article Answer Next?
&lt;/h2&gt;

&lt;p&gt;The previous two articles traced atomic operations from language-level semantics down to the CPU, showing how Atomicity, Visibility, and Ordering are implemented.&lt;/p&gt;

&lt;p&gt;This article follows the same path for &lt;code&gt;volatile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;volatile&lt;/code&gt; does not make a compound update such as &lt;code&gt;counter++&lt;/code&gt; atomic. It is better suited to publication and observation:&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                         Thread B

counter = 1                     if (ready) {
ready = true                        print(counter)
                                }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The required guarantee is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If Thread B observes &lt;code&gt;ready == true&lt;/code&gt;, a subsequent read of &lt;code&gt;counter&lt;/code&gt; must observe &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We first establish this guarantee in the Java Memory Model, then trace how HotSpot preserves it down to the CPU.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What Guarantees Does Java volatile Provide at the Language Level?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Visibility and Ordering
&lt;/h3&gt;

&lt;p&gt;Declare &lt;code&gt;ready&lt;/code&gt; as &lt;code&gt;volatile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Thread A&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Thread B&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;counter&lt;/code&gt; remains an ordinary field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-17.html#jls-17.4.4" rel="noopener noreferrer"&gt;JLS §17.4.4&lt;/a&gt; states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“A write to a volatile variable v synchronizes-with all subsequent reads of v by any thread.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So if Thread B's volatile read observes the value &lt;code&gt;true&lt;/code&gt; written by Thread A, the relationship is:&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                              Thread B

counter = 1
    │
    │ program order
    ▼
volatile ready = true ── synchronizes-with ──► read volatile ready == true
                                                   │
                                                   │ program order
                                                   ▼
                                               read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By transitivity of happens-before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;write counter
    ↓ happens-before
write volatile ready
    ↓ synchronizes-with
read volatile ready
    ↓ happens-before
read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once B observes &lt;code&gt;ready == true&lt;/code&gt;, the earlier &lt;code&gt;counter = 1&lt;/code&gt; write must be visible to B. The following outcome is therefore forbidden:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ready == true
counter == 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives us two guarantees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visibility&lt;/strong&gt;: writes completed by A before the volatile write become visible to B after B observes that write;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordering&lt;/strong&gt;: the operations must be observed in a way that is consistent with the happens-before relationship.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1.2 volatile Does Not Make Compound Operations Atomic
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;counter++&lt;/code&gt; is still a read-modify-write operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;volatile read
      ↓
     add
      ↓
volatile write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;volatile&lt;/code&gt; constrains the visibility and ordering of the individual read and write, but it does not make the entire &lt;code&gt;read → add → write&lt;/code&gt; sequence indivisible.&lt;/p&gt;

&lt;p&gt;With multiple threads incrementing concurrently, updates can still be lost.&lt;/p&gt;

&lt;p&gt;If you need an atomic RMW, use &lt;code&gt;AtomicInteger&lt;/code&gt;, CAS, or a mutex.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. How Does HotSpot Implement volatile?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 Layers
&lt;/h3&gt;

&lt;p&gt;As in the Mutex and Atomic articles, first establish the implementation stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java Source Code
Example: volatile boolean ready
Role: declare a field with volatile memory semantics
        │
        ▼
JVM Bytecode
Example: getfield / putfield (or getstatic / putstatic)
Field metadata: ACC_VOLATILE
Role: perform ordinary field access; volatility is carried by field metadata
        │
        ▼
JVM Implementation (HotSpot)
Example: field-&amp;gt;is_volatile() / MO_SEQ_CST / MemBar
Role: preserve and implement volatile memory-ordering semantics
        │
        ▼
x86-64 Hardware
Example: Load / Store / Cache Coherence / Fence
Role: provide the hardware basis for Visibility and Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This differs from both &lt;code&gt;synchronized&lt;/code&gt; and &lt;code&gt;AtomicInteger&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;synchronized
  → dedicated monitorenter / monitorexit bytecodes

AtomicInteger
  → ordinary invokevirtual
  → no Atomic-specific bytecode

volatile
  → still uses getfield / putfield
  → no dedicated volatile-load / volatile-store bytecode
  → the volatile property is recorded in ACC_VOLATILE field metadata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 End-to-End Implementation Path for a volatile Write and Read
&lt;/h3&gt;

&lt;p&gt;Continue with the same &lt;code&gt;counter / ready&lt;/code&gt; example:&lt;/p&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%2Fv56hu0zvgwglhon6aosm.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%2Fv56hu0zvgwglhon6aosm.png" alt="How the ordinary write to counter and the volatile write/read of ready pass through JVM Bytecode and HotSpot before reaching x86-64 Hardware." width="800" height="897"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key point is not a particular machine instruction. HotSpot must recognize that &lt;code&gt;ready&lt;/code&gt; is volatile and preserve its memory semantics all the way to the target architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Guarantees at the Runtime / Language Implementation Layer
&lt;/h3&gt;

&lt;h4&gt;
  
  
  2.3.1 Visibility
&lt;/h4&gt;

&lt;p&gt;HotSpot distinguishes ordinary fields from volatile fields while parsing field accesses. In the current C2 path, volatile accesses carry memory-order information such as &lt;code&gt;MO_SEQ_CST&lt;/code&gt;, while ordinary fields use unordered access semantics.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ordinary field
  → ordinary load / store

volatile field
  → volatile load / store
  → preserve cross-thread observable memory semantics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HotSpot therefore cannot optimize a volatile access as if it were an ordinary field access—for example, by eliminating, merging, or moving it across synchronization boundaries that matter.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3.2 Ordering
&lt;/h4&gt;

&lt;p&gt;HotSpot must also constrain reordering around volatile accesses.&lt;/p&gt;

&lt;p&gt;In C2's IR, the typical structure includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;volatile store
  ← ordering constraints such as MemBarRelease

volatile load
  → ordering constraints such as MemBarAcquire
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cases that require stronger ordering—such as volatile store → volatile load—also require the corresponding StoreLoad / volatile-barrier constraint.&lt;/p&gt;

&lt;p&gt;These are IR nodes and compiler-ordering constraints, not a one-to-one promise that every barrier becomes a separate CPU fence instruction.&lt;/p&gt;

&lt;p&gt;Relevant OpenJDK sources include &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/opto/parse3.cpp" rel="noopener noreferrer"&gt;&lt;code&gt;parse3.cpp&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/opto/memnode.hpp" rel="noopener noreferrer"&gt;&lt;code&gt;memnode.hpp&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/cpu/x86/templateTable_x86.cpp" rel="noopener noreferrer"&gt;&lt;code&gt;templateTable_x86.cpp&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 Guarantees at the Hardware Layer
&lt;/h3&gt;

&lt;p&gt;At the hardware layer, these guarantees reduce to the same two capabilities introduced earlier in the series:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visibility
  → Cache Coherence
  → other CPU cores cannot keep using an invalidated old cache line indefinitely

Ordering
  → CPU Memory Ordering + Fence / equivalent ordering constraints
  → preserve the memory-access order required at volatile boundaries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;x86-64 already provides relatively strong memory ordering, so many Acquire / Release constraints do not require a standalone hardware fence instruction.&lt;/p&gt;

&lt;p&gt;When a StoreLoad fence is required, HotSpot's Linux x86 path can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lock; addl $0, 0(%rsp)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to provide a full-fence effect.&lt;/p&gt;

&lt;p&gt;See OpenJDK &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/os_cpu/linux_x86/orderAccess_linux_x86.hpp" rel="noopener noreferrer"&gt;&lt;code&gt;orderAccess_linux_x86.hpp&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Why Don't Go and CPython Have Java-style volatile?
&lt;/h2&gt;

&lt;p&gt;Go and Python do not expose Java's field-level &lt;code&gt;volatile&lt;/code&gt; mechanism. The reasons are different, so consider them separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Go: A Language Design Choice
&lt;/h3&gt;

&lt;p&gt;Effective Go summarizes one of Go's central concurrency design principles with a well-known rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Do not communicate by sharing memory; instead, share memory by communicating.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That reflects a language-design choice: &lt;strong&gt;synchronization should be expressed through explicit concurrency primitives rather than hidden in field modifiers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Accordingly, Go does not provide a Java-style field-level &lt;code&gt;volatile&lt;/code&gt; modifier. It expresses the same kinds of synchronization through channels, mutexes, and atomic operations.&lt;/p&gt;

&lt;p&gt;For the use case closest to Java &lt;code&gt;volatile&lt;/code&gt;, Go uses &lt;code&gt;sync/atomic&lt;/code&gt;. The Go Memory Model states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“This definition provides the same semantics as C++'s sequentially consistent atomics and Java's volatile variables.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use the same &lt;code&gt;counter / ready&lt;/code&gt; example:&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;var&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;

&lt;span class="c"&gt;// Goroutine A&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Goroutine B&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&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;If &lt;code&gt;ready.Load()&lt;/code&gt; observes the value written by &lt;code&gt;ready.Store(true)&lt;/code&gt;, the two atomic operations establish a synchronized-before relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine A                          Goroutine B

counter = 1
    │
    │ sequenced-before
    ▼
ready.Store(true) ── synchronized-before ──► ready.Load() == true
                                                 │
                                                 │ sequenced-before
                                                 ▼
                                             read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So once B observes &lt;code&gt;ready == true&lt;/code&gt;, the earlier &lt;code&gt;counter = 1&lt;/code&gt; write must be visible to B.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 CPython: Synchronization Remains a Runtime Responsibility
&lt;/h3&gt;

&lt;p&gt;Python does not expose Java-style &lt;code&gt;volatile&lt;/code&gt;. The key reason is not syntax; synchronization has historically been a responsibility of the CPython runtime.&lt;/p&gt;

&lt;p&gt;There are two main points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Traditional CPython uses the GIL to cover many Visibility / Ordering concerns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The CPython C API documentation states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“only a thread that holds the GIL may operate on Python objects or invoke Python’s C API.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In traditional CPython, the GIL serializes many operations on Python objects.&lt;/p&gt;

&lt;p&gt;That serialization also provides many of the practical Visibility and Ordering effects that Java code might otherwise obtain from &lt;code&gt;volatile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The abstractions are still different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java volatile
  → field-level language semantics

CPython GIL
  → runtime-level synchronization mechanism
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Free-threaded CPython still uses runtime locks and atomics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Making the GIL optional did not introduce field-level &lt;code&gt;volatile&lt;/code&gt; semantics.&lt;/p&gt;

&lt;p&gt;PEP 703 instead says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“This PEP proposes using per-object locks...”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The runtime also uses atomic operations internally.&lt;/p&gt;

&lt;p&gt;So the implementation shifts from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Per-object lock + atomic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Synchronization therefore remains a runtime responsibility rather than becoming part of Python's field-level language semantics.&lt;/p&gt;

&lt;p&gt;At the application level, publication and observation are expressed through synchronization objects. The documentation for &lt;code&gt;threading.Event&lt;/code&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“one thread signals an event and other threads wait for it.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use the same &lt;code&gt;counter / ready&lt;/code&gt; example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Thread A
&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Thread B
&lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;Event&lt;/code&gt; plays the synchronization role that &lt;code&gt;ready&lt;/code&gt; played in the Java example:&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                         Thread B

counter = 1
    │
    ▼
ready.set()  ───────────────►  ready.wait() returns
                                  │
                                  ▼
                              read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So Python application code uses Event / Lock for synchronization, while lower-level atomics and memory ordering remain implementation details of the CPython runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. How volatile, Atomics, and Mutexes Differ
&lt;/h2&gt;

&lt;p&gt;The three abstractions operate at different granularities:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;volatile&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Atomic&lt;/th&gt;
&lt;th&gt;Mutex&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core problem&lt;/td&gt;
&lt;td&gt;publish / observe shared state&lt;/td&gt;
&lt;td&gt;one shared-state operation&lt;/td&gt;
&lt;td&gt;a critical section&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Atomicity&lt;/td&gt;
&lt;td&gt;does not make compound RMW atomic&lt;/td&gt;
&lt;td&gt;one atomic operation is indivisible&lt;/td&gt;
&lt;td&gt;critical section is mutually exclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visibility&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordering&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use&lt;/td&gt;
&lt;td&gt;initialization / configuration / stop flags; state publication&lt;/td&gt;
&lt;td&gt;counters / Add / CAS / Swap&lt;/td&gt;
&lt;td&gt;multi-step logic / multi-field updates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A compact way to view the relationship is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;volatile
  → establish Visibility / Ordering between a publication and a later observation

Atomic
  → make one shared-state operation indivisible
  → also provide the corresponding Visibility / Ordering

Mutex
  → use lower-level primitives such as atomics to arbitrate lock state
  → then protect an entire critical section
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are not simply stronger and weaker versions of the same tool. They solve different problems at different granularities.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Next: From Mutexes to Read-Write Locks
&lt;/h2&gt;

&lt;p&gt;A Mutex allows only one execution unit to enter a critical section at a time.&lt;/p&gt;

&lt;p&gt;If most operations only read shared state, making readers exclude one another is unnecessary.&lt;/p&gt;

&lt;p&gt;The next article looks at read-write locks: multiple readers can proceed concurrently, while writers still retain exclusive access.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.github.io/en/articles/concurrency-series-07-volatile/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>backend</category>
      <category>computerscience</category>
      <category>java</category>
      <category>programming</category>
    </item>
    <item>
      <title>Concurrency Programming (6): Atomic Implementation — From Runtime to CPU</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Thu, 01 Oct 2026 06:36:04 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/concurrency-programming-6-atomic-implementation-from-runtime-to-cpu-3nnn</link>
      <guid>https://dev.to/thinkerqaq/concurrency-programming-6-atomic-implementation-from-runtime-to-cpu-3nnn</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;0. What Does This Article Continue to Answer?&lt;/li&gt;
&lt;li&gt;
1. How Does HotSpot Implement AtomicInteger?

&lt;ul&gt;
&lt;li&gt;1.1 Layers&lt;/li&gt;
&lt;li&gt;1.2 A Complete AtomicInteger Implementation Path&lt;/li&gt;
&lt;li&gt;1.3 Atomicity&lt;/li&gt;
&lt;li&gt;1.4 Visibility and Ordering&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
2. How Does Go Implement sync/atomic?

&lt;ul&gt;
&lt;li&gt;2.1 Layers&lt;/li&gt;
&lt;li&gt;2.2 A Complete atomic.Int64 Implementation Path&lt;/li&gt;
&lt;li&gt;2.3 Atomicity&lt;/li&gt;
&lt;li&gt;2.4 Visibility and Ordering&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
3. How Does CPython Implement Internal Atomics?

&lt;ul&gt;
&lt;li&gt;3.1 Layers&lt;/li&gt;
&lt;li&gt;3.2 A Complete CPython Atomic Implementation Path&lt;/li&gt;
&lt;li&gt;3.3 Atomicity&lt;/li&gt;
&lt;li&gt;3.4 Visibility and Ordering&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
4. The Common Pattern Behind All Three Implementations

&lt;ul&gt;
&lt;li&gt;4.1 How Does a Single-variable Update Become Indivisible? — Atomic RMW&lt;/li&gt;
&lt;li&gt;4.2 What Happens When a Conditional Update Fails? — CAS / Retry&lt;/li&gt;
&lt;li&gt;4.3 Why Can Atomics Also Establish Visibility and Ordering?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5. Next: volatile&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  0. What Does This Article Continue to Answer?
&lt;/h2&gt;

&lt;p&gt;The previous article described the atomicity, visibility, and ordering guarantees exposed by Atomic APIs at the language level. This article follows those guarantees down through the compiler and runtime to the CPU.&lt;/p&gt;

&lt;p&gt;We continue to use the same two examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter = 1
ready = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation path can be summarized as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Language API and memory model
        ↓
Compiler / Runtime
        ↓
CPU Atomic Instruction / Memory Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  1. How Does HotSpot Implement AtomicInteger?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Layers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java API
Example: AtomicInteger.incrementAndGet() / compareAndSet() / get()
Role: declare single-variable Atomic operations
        │
        ▼
JDK Implementation
Example: AtomicInteger / Unsafe / VarHandle Memory Effects
Role: express Atomic RMW, CAS, and memory semantics
        │
        ▼
JVM Implementation (HotSpot)
Example: Intrinsic / Atomic Node
Role: map Atomic operations to the target architecture
        │
        ▼
x86-64 Hardware
Example: LOCK XADD / LOCK CMPXCHG / Load
Role: provide the hardware basis for Atomicity, Visibility, and Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1.2 A Complete AtomicInteger Implementation Path
&lt;/h3&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%2Fs284p5ws1iyw3c1okasy.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%2Fs284p5ws1iyw3c1okasy.png" alt="How incrementAndGet travels through AtomicInteger, Unsafe, HotSpot, and x86-64 hardware to complete one atomic RMW." width="799" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common Atomic operations can be expanded as:&lt;/p&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%2Fgjasfs3bwb356jtuosaw.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%2Fgjasfs3bwb356jtuosaw.png" alt="Add, CAS, and Load pass through Unsafe and HotSpot intrinsics, then map to atomic RMW, compare-and-swap, and an appropriately ordered load." width="800" height="975"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 Atomicity
&lt;/h3&gt;

&lt;p&gt;On x86-64, an atomic addition can be simplified as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov  eax, 1
lock xadd dword ptr [counter], eax
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;XADD&lt;/code&gt; reads the old value and writes &lt;code&gt;old + 1&lt;/code&gt;; the &lt;code&gt;LOCK&lt;/code&gt; prefix makes the whole RMW indivisible with respect to competing cores.&lt;/p&gt;

&lt;p&gt;CAS can be simplified as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;; EAX contains expected
; ECX contains newValue
lock cmpxchg dword ptr [counter], ecx
sete al
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These snippets illustrate equivalent machine-level operations; they are not a claim about the exact full instruction sequence emitted by every JVM version.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;counter + ready&lt;/code&gt;, HotSpot must also constrain compile-time reordering according to the JMM and the memory effects of the specific Atomic method, then map those constraints onto x86-64 memory ordering. Looking only at a &lt;code&gt;LOCK&lt;/code&gt; instruction and ignoring the compiler layer would miss part of the implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.4 Visibility and Ordering
&lt;/h3&gt;

&lt;p&gt;Java Atomic methods carry explicit memory effects in addition to indivisible RMW. &lt;code&gt;incrementAndGet()&lt;/code&gt; / &lt;code&gt;getAndAdd()&lt;/code&gt; use the volatile read/write effects of &lt;code&gt;VarHandle.getAndAdd&lt;/code&gt;, while &lt;code&gt;get()&lt;/code&gt; has volatile-read semantics.&lt;/p&gt;

&lt;p&gt;HotSpot must preserve those compiler-ordering constraints before x86-64 memory ordering, cache coherence, and locked RMW provide the hardware behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomicity
  → Atomic Instruction
  → HotSpot / x86-64: LOCK XADD / LOCK CMPXCHG

Visibility
  → Cache Coherence
  → Typical x86-64 CPU: MESI-family (for example MESIF / MOESI)

Ordering
  → Compiler Ordering + Hardware Memory Ordering
  → HotSpot Memory Effects + x86-64 Locked RMW / Load rules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;LOCK XADD&lt;/code&gt; / &lt;code&gt;LOCK CMPXCHG&lt;/code&gt; are typical x86-64 mappings, not a promise that every JVM version or architecture emits the same instruction sequence.&lt;/p&gt;

&lt;p&gt;See OpenJDK &lt;a href="https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/concurrent/atomic/AtomicInteger.java" rel="noopener noreferrer"&gt;&lt;code&gt;AtomicInteger.java&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/jdk/internal/misc/Unsafe.java" rel="noopener noreferrer"&gt;&lt;code&gt;Unsafe.java&lt;/code&gt;&lt;/a&gt;, and HotSpot &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/os_cpu/linux_x86/atomicAccess_linux_x86.hpp" rel="noopener noreferrer"&gt;&lt;code&gt;atomicAccess_linux_x86.hpp&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. How Does Go Implement sync/atomic?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 Layers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go API
Example: atomic.Int64.Add() / CompareAndSwap() / Load()
Role: declare single-variable Atomic operations
        │
        ▼
Go Implementation (sync/atomic)
Example: atomic.Int64 / AddInt64 / CompareAndSwapInt64
Role: express Add, CAS, Load, and related Atomic semantics
        │
        ▼
Go Runtime Atomic
Example: internal/runtime/atomic
Role: implement target-architecture Atomic primitives
        │
        ▼
x86-64 Hardware
Example: LOCK XADDQ / LOCK CMPXCHGQ / Load
Role: provide the hardware basis for Atomicity, Visibility, and Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 A Complete atomic.Int64 Implementation Path
&lt;/h3&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%2Fa1vkkv2deddh3ofbbk2a.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%2Fa1vkkv2deddh3ofbbk2a.png" alt="How atomic.Int64.Add travels through sync/atomic, internal/runtime/atomic, and x86-64 hardware to complete one atomic RMW." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common Atomic operations can be expanded as:&lt;/p&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%2Fm8zvig83m0h5xe5hv9x6.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%2Fm8zvig83m0h5xe5hv9x6.png" alt="Add, CompareAndSwap, and Load pass through sync/atomic and Runtime Atomic, then map to amd64 XADD, CMPXCHG, and Load." width="800" height="829"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Atomicity
&lt;/h3&gt;

&lt;p&gt;On amd64, atomic addition can reach an &lt;code&gt;XADD&lt;/code&gt; with the &lt;code&gt;LOCK&lt;/code&gt; prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOCK
XADDQ AX, 0(BX)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CAS can reach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOCK
CMPXCHGQ CX, 0(BX)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;counter.Add(1)&lt;/code&gt; can use a hardware atomic-add operation directly rather than first being rewritten as a CAS loop.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;counter + ready&lt;/code&gt;, the compiler must also preserve the ordering required by the Go Memory Model. The amd64 implementation uses strongly ordered atomic instructions; other architectures can use different instruction and barrier combinations while preserving the same observable semantics.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 Visibility and Ordering
&lt;/h3&gt;

&lt;p&gt;The Go Memory Model defines synchronized-before for Atomic operations and requires all Atomic operations to behave as though they execute in some sequentially consistent order. The compiler and Runtime must preserve that observable behavior.&lt;/p&gt;

&lt;p&gt;In the current non-race &lt;code&gt;sync/atomic&lt;/code&gt; path, primitives enter &lt;code&gt;internal/runtime/atomic&lt;/code&gt;; on amd64, Add and CAS then use locked RMW instructions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomicity
  → Atomic Instruction
  → Go / amd64: LOCK XADDQ / LOCK CMPXCHGQ

Visibility
  → Cache Coherence
  → Typical x86-64 CPU: MESI-family (for example MESIF / MOESI)

Ordering
  → Compiler Ordering + Hardware Memory Ordering
  → Go Atomic semantics + amd64 Locked RMW / Load rules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See &lt;a href="https://pkg.go.dev/sync/atomic" rel="noopener noreferrer"&gt;&lt;code&gt;sync/atomic&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://go.dev/src/internal/runtime/atomic/" rel="noopener noreferrer"&gt;&lt;code&gt;internal/runtime/atomic&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://go.dev/src/internal/runtime/atomic/atomic_amd64.s" rel="noopener noreferrer"&gt;&lt;code&gt;atomic_amd64.s&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. How Does CPython Implement Internal Atomics?
&lt;/h2&gt;

&lt;p&gt;This section discusses only the Atomics used inside the current CPython Runtime. It does not turn them into a public application-level Python API.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Layers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPython Runtime
Example: Reference Count / Runtime State / Lock State
Role: create internal shared-state update requirements
        │
        ▼
CPython Atomic Abstraction
Example: _Py_atomic_add_* / _Py_atomic_compare_exchange_* / _Py_atomic_load_*
Role: express Atomic operations and Memory Order
        │
        ▼
C Compiler Atomic Builtin
Example: GCC / Clang __atomic_*
Role: lower Atomic semantics to the target architecture
        │
        ▼
x86-64 Hardware
Example: LOCK XADD / LOCK CMPXCHG / Load
Role: provide the hardware basis for Atomicity, Visibility, and Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 A Complete CPython Atomic Implementation Path
&lt;/h3&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%2Fil1nvwiwfl1ivj8edqk6.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%2Fil1nvwiwfl1ivj8edqk6.png" alt="How an internal CPython atomic add travels through _Py_atomic, GCC or Clang Atomic Builtins, and x86-64 hardware." width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common internal Atomic operations can be expanded as:&lt;/p&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%2Ft0n4pverl7iyq3r9v9tz.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%2Ft0n4pverl7iyq3r9v9tz.png" alt="CPython Runtime expresses Add, Compare-Exchange, and Load/Store through _Py_atomic and Memory Order, then compiler builtins map them to x86-64." width="800" height="877"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Atomicity
&lt;/h3&gt;

&lt;p&gt;On Linux x86-64 with GCC or Clang, an atomic addition can typically compile to &lt;code&gt;LOCK XADD&lt;/code&gt;, while CAS can typically compile to &lt;code&gt;LOCK CMPXCHG&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_Py_atomic_add_*
        ↓
__atomic_fetch_add
        ↓
LOCK XADD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_Py_atomic_compare_exchange_*
        ↓
__atomic_compare_exchange_n
        ↓
LOCK CMPXCHG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact instruction depends on compiler version, target architecture, operand width, and requested memory order, so these are representative paths rather than universal instruction listings.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Visibility and Ordering
&lt;/h3&gt;

&lt;p&gt;CPython's &lt;code&gt;_Py_atomic_*&lt;/code&gt; layer supports more than one memory order. Current implementations include default sequentially consistent operations as well as variants such as &lt;code&gt;_relaxed&lt;/code&gt;, &lt;code&gt;_acquire&lt;/code&gt;, and &lt;code&gt;_release&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Visibility and Ordering therefore depend on the Memory Order selected at the call site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPython Runtime
  → choose _Py_atomic_* Memory Order
  → GCC / Clang __atomic_* order
  → x86-64 Load / Store / Locked RMW and architecture ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mapped back to the hardware model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomicity
  → Atomic Instruction
  → x86-64: LOCK XADD / LOCK CMPXCHG

Visibility
  → Cache Coherence
  → Typical x86-64 CPU: MESI-family (for example MESIF / MOESI)

Ordering
  → Compiler Atomic Memory Order + Hardware Memory Ordering
  → __atomic_* order + x86-64 ordering rules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an implementation contract of the CPython Runtime, not a Python-language Memory Model.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://github.com/python/cpython/blob/main/Include/cpython/pyatomic.h" rel="noopener noreferrer"&gt;&lt;code&gt;pyatomic.h&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/python/cpython/blob/main/Include/cpython/pyatomic_gcc.h" rel="noopener noreferrer"&gt;&lt;code&gt;pyatomic_gcc.h&lt;/code&gt;&lt;/a&gt;, and a real use site in &lt;a href="https://github.com/python/cpython/blob/main/Python/lock.c" rel="noopener noreferrer"&gt;&lt;code&gt;Python/lock.c&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Common Pattern Behind All Three Implementations
&lt;/h2&gt;

&lt;p&gt;Java, Go, and CPython expose different APIs, but their Atomic implementations converge on the same three problems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. How can one update become indivisible?
2. If a conditional update fails, who decides whether to retry?
3. How do ordinary reads and writes around the atomic operation gain the required visibility and ordering?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.1 How Does a Single-variable Update Become Indivisible? — Atomic RMW
&lt;/h3&gt;

&lt;p&gt;An ordinary &lt;code&gt;read → modify → write&lt;/code&gt; contains several interleavable steps. Atomic Add, Exchange, CAS, and other RMW operations hand that shared-state update to a CPU Atomic Instruction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Language / Runtime Atomic
        ↓
Compiler / Intrinsic
        ↓
Atomic RMW Instruction
        ↓
Cache Coherence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On x86-64, typical examples are &lt;code&gt;LOCK XADD&lt;/code&gt; and &lt;code&gt;LOCK CMPXCHG&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 What Happens When a Conditional Update Fails? — CAS / Retry
&lt;/h3&gt;

&lt;p&gt;CAS performs one atomic "compare and conditionally write":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expected still matches
  → update succeeds

expected is stale
  → update fails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CAS itself does not promise unlimited retries. &lt;code&gt;compareAndSet()&lt;/code&gt; / &lt;code&gt;CompareAndSwap()&lt;/code&gt; may simply return failure. &lt;code&gt;updateAndGet()&lt;/code&gt;, some Runtime algorithms, or caller-written CAS loops are the layers that decide to reload, recompute, and retry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CAS
= one atomic conditional update

CAS Loop
= CAS + algorithm-level retry after failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.3 Why Can Atomics Also Establish Visibility and Ordering?
&lt;/h3&gt;

&lt;p&gt;Indivisible RMW solves only Atomicity.&lt;/p&gt;

&lt;p&gt;Java VarHandle Memory Effects, the Go Memory Model's Atomic rules, and the Memory Order chosen by CPython &lt;code&gt;_Py_atomic_*&lt;/code&gt; also constrain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compiler Reordering
      +
CPU Memory Ordering
      +
Cache Coherence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the full path remains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Language / Runtime defines Memory Semantics
        ↓
Compiler preserves ordering constraints
        ↓
CPU uses Atomic Instruction / Load / Store / Fence mechanisms
        ↓
Cache Coherence propagates shared state
        ↓
Program receives the corresponding Visibility and Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also where Atomics and the previous Mutex article converge at the bottom: both depend on Atomic Instructions, Cache Coherence, and Memory Ordering. Mutexes additionally need waiting and wakeup paths under serious contention; the Atomic operation itself has no Park / Wakeup flow.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Next: volatile
&lt;/h2&gt;

&lt;p&gt;Atomic operations address the atomicity of RMW updates such as &lt;code&gt;counter++&lt;/code&gt;. If no atomic update is needed and the goal is only to use &lt;code&gt;ready&lt;/code&gt; to publish an already-written &lt;code&gt;counter&lt;/code&gt;, Java provides &lt;code&gt;volatile&lt;/code&gt;. The next article explains how it provides visibility and ordering, while also explaining why Go and Python do not have a corresponding &lt;code&gt;volatile&lt;/code&gt; keyword.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.github.io/en/articles/concurrency-series-06-atomic-implementation/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>computerscience</category>
      <category>go</category>
      <category>java</category>
      <category>programming</category>
    </item>
    <item>
      <title>Concurrency Programming (5): Atomics — Atomicity, Visibility, and Ordering at the Language Level</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Tue, 29 Sep 2026 05:58:37 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/concurrency-programming-5-atomics-atomicity-visibility-and-ordering-at-the-language-level-1idc</link>
      <guid>https://dev.to/thinkerqaq/concurrency-programming-5-atomics-atomicity-visibility-and-ordering-at-the-language-level-1idc</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;0. Continue from the Previous Article&lt;/li&gt;
&lt;li&gt;
1. What Guarantees Do the Atomic Rules in Each Language Provide?

&lt;ul&gt;
&lt;li&gt;1.1 Java: AtomicInteger&lt;/li&gt;
&lt;li&gt;1.2 Go: sync/atomic&lt;/li&gt;
&lt;li&gt;1.3 CPython: No Symmetric Application-level Atomic API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2. Next: How Are Atomics Implemented?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  0. Continue from the Previous Article
&lt;/h2&gt;

&lt;p&gt;This article discusses Atomics directly from the rules defined by language memory models and public APIs.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What Guarantees Do the Atomic Rules in Each Language Provide?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Java: AtomicInteger
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1.1.1 Atomicity
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/atomic/AtomicInteger.html#incrementAndGet()" rel="noopener noreferrer"&gt;&lt;code&gt;AtomicInteger.incrementAndGet()&lt;/code&gt;&lt;/a&gt; states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Atomically increments the current value, with memory effects as specified by &lt;code&gt;VarHandle.getAndAdd&lt;/code&gt;.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that &lt;code&gt;incrementAndGet()&lt;/code&gt; is one atomic increment. Reading the old value, adding one, and writing the result back are not three independently interleavable steps; together they form one Atomic RMW operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AtomicInteger&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AtomicInteger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Thread A&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Thread B&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two updates apply to the same value in sequence:&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                    Thread B

incrementAndGet()           incrementAndGet()
        │                           │
        ▼                           ▼
      0 → 1                       1 → 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A lost update like the one possible with a plain &lt;code&gt;counter++&lt;/code&gt; therefore does not occur.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.1.2 Visibility
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;incrementAndGet()&lt;/code&gt; gets its memory effects from &lt;code&gt;VarHandle.getAndAdd&lt;/code&gt;. &lt;a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/invoke/VarHandle.html#getAndAdd(java.lang.Object...)" rel="noopener noreferrer"&gt;&lt;code&gt;VarHandle.getAndAdd&lt;/code&gt;&lt;/a&gt; describes its write side as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“with the memory semantics of &lt;code&gt;setVolatile(Object...)&lt;/code&gt;”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/atomic/AtomicInteger.html#get()" rel="noopener noreferrer"&gt;&lt;code&gt;AtomicInteger.get()&lt;/code&gt;&lt;/a&gt; uses &lt;code&gt;VarHandle.getVolatile&lt;/code&gt; read semantics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-17.html#jls-17.4.4" rel="noopener noreferrer"&gt;JLS §17.4.4 Synchronization Order&lt;/a&gt; says that a volatile write:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“synchronizes-with all subsequent reads of v by any thread”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the volatile write performed by the Atomic update can establish a &lt;strong&gt;synchronizes-with&lt;/strong&gt; relationship with a later volatile read in another thread.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AtomicInteger&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AtomicInteger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Thread A&lt;/span&gt;
&lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;                 &lt;span class="c1"&gt;// A1&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;    &lt;span class="c1"&gt;// A2&lt;/span&gt;

&lt;span class="c1"&gt;// Thread B&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;     &lt;span class="c1"&gt;// B1&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// B2&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assume B's &lt;code&gt;counter.get()&lt;/code&gt; reads the &lt;code&gt;1&lt;/code&gt; produced by A's update:&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                              Thread B

A1  ready = true
        │
        │ program order
        ▼
A2  counter.incrementAndGet()
        │
        │ synchronizes-with
        │ JLS §17.4.4
        └──────────────────────────► B1  counter.get() == 1
                                        │
                                        │ program order
                                        ▼
                                    B2  read ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A's earlier &lt;code&gt;ready = true&lt;/code&gt; write is therefore visible to B's later read. That is the Visibility guarantee in this example.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.1.3 Ordering
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-17.html#jls-17.4.5" rel="noopener noreferrer"&gt;JLS §17.4.5 Happens-before Order&lt;/a&gt; states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“If one action happens-before another, then the first is visible to and ordered before the second.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So happens-before describes not only visibility but also the required ordering between the actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A1 ready = true
    ↓ program order
A2 counter.incrementAndGet()
    ↓ synchronizes-with
B1 counter.get() == 1
    ↓ program order
B2 read ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A1 happens-before A2
A2 happens-before B1
B1 happens-before B2

        ↓ transitivity

A1 happens-before B2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once B has observed A's update through &lt;code&gt;counter.get()&lt;/code&gt;, it cannot still observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter == 1
ready == false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the Ordering guarantee.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Go: sync/atomic
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1.2.1 Atomicity
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://pkg.go.dev/sync/atomic#Int64.Add" rel="noopener noreferrer"&gt;&lt;code&gt;atomic.Int64.Add&lt;/code&gt;&lt;/a&gt; states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Add atomically adds delta to x and returns the new value.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;Add(1)&lt;/code&gt; therefore performs the read, increment, and write-back as one atomic update.&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;var&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int64&lt;/span&gt;

&lt;span class="c"&gt;// Goroutine A&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Goroutine B&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two updates do not lose one another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine A                 Goroutine B

Add(1)                      Add(1)
  │                           │
  ▼                           ▼
0 → 1                       1 → 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is Atomicity.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.2.2 Visibility
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://go.dev/ref/mem#atomic" rel="noopener noreferrer"&gt;The Go Memory Model - Atomic Values&lt;/a&gt; states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“If the effect of an atomic operation A is observed by atomic operation B, then A is synchronized before B.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So if B's Atomic operation observes A's Atomic update, the two operations have a &lt;strong&gt;synchronized-before&lt;/strong&gt; relationship.&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;var&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int64&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;

&lt;span class="c"&gt;// Goroutine A&lt;/span&gt;
&lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;          &lt;span class="c"&gt;// A1&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c"&gt;// A2&lt;/span&gt;

&lt;span class="c"&gt;// Goroutine B&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// B1&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c"&gt;// B2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assume B's &lt;code&gt;counter.Load()&lt;/code&gt; observes the &lt;code&gt;1&lt;/code&gt; produced by A's &lt;code&gt;Add(1)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine A                           Goroutine B

A1  ready = true
        │
        │ sequenced-before
        ▼
A2  counter.Add(1)
        │
        │ synchronized-before
        │ Go Memory Model
        └──────────────────────────► B1  counter.Load() == 1
                                        │
                                        │ sequenced-before
                                        ▼
                                    B2  read ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A's earlier &lt;code&gt;ready = true&lt;/code&gt; write is therefore visible to B's later read. That is Visibility.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.2.3 Ordering
&lt;/h4&gt;

&lt;p&gt;The same Atomic Values section says that Atomic operations behave as though they were in:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“some sequentially consistent order”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Go Memory Model defines happens-before from the transitive closure of &lt;code&gt;sequenced-before&lt;/code&gt; and &lt;code&gt;synchronized-before&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A1 ready = true
    ↓ sequenced-before
A2 counter.Add(1)
    ↓ synchronized-before
B1 counter.Load() == 1
    ↓ sequenced-before
B2 read ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A1 happens-before A2
A2 happens-before B1
B1 happens-before B2

        ↓ transitivity

A1 happens-before B2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After B has observed &lt;code&gt;counter == 1&lt;/code&gt;, it cannot treat A's earlier &lt;code&gt;ready = true&lt;/code&gt; as if it had not happened. That is Ordering.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 CPython: No Symmetric Application-level Atomic API
&lt;/h3&gt;

&lt;p&gt;Python's standard library currently has no general integer Atomic API symmetric with Java's &lt;code&gt;AtomicInteger&lt;/code&gt; or Go's &lt;code&gt;atomic.Int64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Application code therefore has no corresponding set of public Atomic rules to cite. An ordinary &lt;code&gt;counter += 1&lt;/code&gt; is not a language-defined Atomic API. The existence of the GIL in a particular CPython configuration, or the use of &lt;code&gt;_Py_atomic_*&lt;/code&gt; inside the Runtime, does not turn it into a cross-implementation guarantee of Atomicity, Visibility, or Ordering for application code.&lt;/p&gt;

&lt;p&gt;CPython itself needs Atomics for shared Runtime state. How &lt;code&gt;_Py_atomic_*&lt;/code&gt; reaches the compiler and CPU is an implementation question for the next article, not part of Python's public application-level Atomic semantics.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Next: How Are Atomics Implemented?
&lt;/h2&gt;

&lt;p&gt;This article stops at the language level: Java and Go define Atomicity, Visibility, and Ordering through public Atomic APIs and their memory models, while Python application code has no symmetric general-purpose Atomic API.&lt;/p&gt;

&lt;p&gt;The next article turns those language-level rules into implementation-level problems by following Java &lt;code&gt;AtomicInteger&lt;/code&gt;, Go &lt;code&gt;sync/atomic&lt;/code&gt;, and CPython's internal &lt;code&gt;_Py_atomic_*&lt;/code&gt; path from the Runtime down to the CPU.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.github.io/en/articles/concurrency-series-05-atomic-cas/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>backend</category>
      <category>go</category>
      <category>java</category>
    </item>
    <item>
      <title>Concurrency Programming (4): Mutex Implementation — From Runtime to CPU</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Mon, 28 Sep 2026 11:20:57 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/concurrency-programming-4-mutex-implementation-from-runtime-to-cpu-1j5n</link>
      <guid>https://dev.to/thinkerqaq/concurrency-programming-4-mutex-implementation-from-runtime-to-cpu-1j5n</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;0. What Does This Article Continue to Answer?&lt;/li&gt;
&lt;li&gt;
1. How Does HotSpot Implement synchronized?

&lt;ul&gt;
&lt;li&gt;1.1 Layers&lt;/li&gt;
&lt;li&gt;1.2 The Complete Path of One synchronized Operation&lt;/li&gt;
&lt;li&gt;1.3 Atomicity&lt;/li&gt;
&lt;li&gt;1.4 Visibility and Ordering&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
2. How Does the Go Runtime Implement sync.Mutex?

&lt;ul&gt;
&lt;li&gt;2.1 Layers&lt;/li&gt;
&lt;li&gt;2.2 The Complete Path of One sync.Mutex Operation&lt;/li&gt;
&lt;li&gt;2.3 Atomicity&lt;/li&gt;
&lt;li&gt;2.4 Visibility and Ordering&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
3. How Does CPython Implement threading.Lock?

&lt;ul&gt;
&lt;li&gt;3.1 Layers&lt;/li&gt;
&lt;li&gt;3.2 The Complete Path of One threading.Lock Operation&lt;/li&gt;
&lt;li&gt;3.3 Atomicity&lt;/li&gt;
&lt;li&gt;3.4 Visibility and Ordering&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
4. The Common Pattern Across the Three Implementations

&lt;ul&gt;
&lt;li&gt;4.1 Who Gets In? — Atomically Modify Lock State&lt;/li&gt;
&lt;li&gt;4.2 What Happens If You Don't Get the Lock? — Spin / Park / Wakeup&lt;/li&gt;
&lt;li&gt;4.3 After Acquiring the Lock, Why Can You See the Data Left by the Previous Holder?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5. Next: Atomic&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  0. What Does This Article Continue to Answer?
&lt;/h2&gt;

&lt;p&gt;The previous article started from the rules of the language memory model and looked at how a Mutex provides Atomicity, Visibility, and Ordering.&lt;/p&gt;

&lt;p&gt;This article continues downward: how Java, Go, and CPython implement those guarantees at the Runtime and CPU layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. How Does HotSpot Implement synchronized?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Layers
&lt;/h3&gt;

&lt;p&gt;First, fix the implementation layers for Java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java Language
Example: synchronized
Role: declare a synchronized region
        │
        ▼
JVM Bytecode
Example: monitorenter / monitorexit (synchronized blocks)
Role: express entering / exiting a Monitor
        │
        ▼
JVM Implementation (HotSpot)
Example: Synchronization Runtime
Role: implement the Monitor semantics required by the JVM
        │
        ▼
x86-64 Hardware
Example: Atomic Instruction / Cache Coherence / Fence
Role: provide the hardware foundation for Atomicity, Visibility, and Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1.2 The Complete Path of One synchronized Operation
&lt;/h3&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%2F7t3kerkhvag52dr3lcx8.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%2F7t3kerkhvag52dr3lcx8.png" alt="The main lock, contention, critical-section, and unlock path from Java Code through JVM Bytecode, HotSpot, OS Thread, and x86-64 Hardware." width="800" height="962"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sequence diagram above keeps only the main cross-layer path. The contention path inside the Runtime can be expanded further:&lt;/p&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%2Fnzs5csdyyduir2cn5b9n.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%2Fnzs5csdyyduir2cn5b9n.png" alt="monitorenter first tries the Lightweight Locking fast path. On failure it performs fast-lock spinning before inflating to ObjectMonitor, where contention continues through CAS, Spin, and Park." width="800" height="3121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 Atomicity
&lt;/h3&gt;

&lt;p&gt;Atomicity corresponds to the two atomic competitions in the flowchart above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fast Path: CAS markWord lock bits [Acquire]
Inflated Monitor: CAS _owner [Acquire]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both atomically modify lock state. When multiple threads compete at the same time, only one thread can successfully acquire the lock and enter the critical section.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.4 Visibility and Ordering
&lt;/h3&gt;

&lt;p&gt;Visibility and Ordering correspond to the lock boundaries in the flowchart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Acquire lock: CAS ... [Acquire]
Release lock: release lock state [Release]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Release / Acquire connects the two critical sections so that writes from the previous lock holder can be observed by the next lock holder in the correct order.&lt;/p&gt;

&lt;p&gt;Going further down, these guarantees are implemented through CPU Cache Coherence and memory-ordering constraints.&lt;/p&gt;

&lt;p&gt;So Java maps back to the hardware model from the first article as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomicity
  → Atomic Instruction
  → HotSpot / x86-64: LOCK CMPXCHG

Visibility
  → Cache Coherence
  → Typical x86-64 CPUs: MESI-family (e.g. MESIF / MOESI)

Ordering
  → Fence
  → HotSpot / x86-64: LOCK ADDL $0, 0(%rsp)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;LOCK ADDL $0, 0(%rsp)&lt;/code&gt; is the actual full-fence path used by HotSpot on Linux x86. This does not mean every &lt;code&gt;synchronized&lt;/code&gt; operation executes an extra copy of that instruction; x86 ordering rules and LOCKed RMW operations also participate in establishing the required ordering.&lt;/p&gt;

&lt;p&gt;For implementation details, see OpenJDK's &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/oops/markWord.hpp" rel="noopener noreferrer"&gt;&lt;code&gt;markWord.hpp&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/objectMonitor.inline.hpp" rel="noopener noreferrer"&gt;&lt;code&gt;objectMonitor.inline.hpp&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/opto/graphKit.cpp" rel="noopener noreferrer"&gt;&lt;code&gt;graphKit.cpp&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/os_cpu/linux_x86/orderAccess_linux_x86.hpp" rel="noopener noreferrer"&gt;&lt;code&gt;orderAccess_linux_x86.hpp&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. How Does the Go Runtime Implement sync.Mutex?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 Layers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go API
Example: sync.Mutex / Lock() / Unlock()
Role: declare a mutual-exclusion boundary
        │
        ▼
Go Implementation (sync / internal/sync)
Example: Mutex
Role: implement sync.Mutex semantics
        │
        ▼
Go Runtime / Scheduler
Example: Semaphore
Role: handle Goroutine waiting / wakeup
        │
        ▼
x86-64 Hardware
Example: Atomic Instruction / Cache Coherence / Fence
Role: provide the hardware foundation for Atomicity, Visibility, and Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 The Complete Path of One sync.Mutex Operation
&lt;/h3&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%2Fwqqjp0v6jgqiuq8m1vwc.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%2Fwqqjp0v6jgqiuq8m1vwc.png" alt="How Lock and Unlock move through sync internal/sync, the Go Runtime Scheduler, and x86-64 Hardware for atomic competition, waiting, wakeup, and release." width="800" height="996"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sequence diagram above keeps only the main cross-layer path. The contention path inside sync.Mutex can be expanded further:&lt;/p&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%2Fknxdgxlckpwnj9pegg7u.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%2Fknxdgxlckpwnj9pegg7u.png" alt="Lock first tries the fast-path CAS. On failure it enters lockSlow, may spin, update state, and wait on the runtime semaphore. Unlock atomically releases the lock and wakes a waiting Goroutine when needed." width="800" height="2319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Atomicity
&lt;/h3&gt;

&lt;p&gt;Atomicity corresponds to the atomic lock-state modifications in the flow above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Acquire lock: CompareAndSwapInt32(&amp;amp;state, 0, mutexLocked)
Release lock: AddInt32(&amp;amp;state, -mutexLocked)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When multiple Goroutines compete at the same time, only one can successfully change &lt;code&gt;state&lt;/code&gt; from unlocked to locked and enter the critical section.&lt;/p&gt;

&lt;p&gt;On amd64, these two classes of atomic operation map to &lt;code&gt;LOCK CMPXCHGL&lt;/code&gt; and &lt;code&gt;LOCK XADDL&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 Visibility and Ordering
&lt;/h3&gt;

&lt;p&gt;Visibility and Ordering correspond to the synchronization boundary formed by &lt;code&gt;Lock / Unlock&lt;/code&gt;. After one holder completes &lt;code&gt;Unlock&lt;/code&gt;, a later Goroutine that successfully executes &lt;code&gt;Lock&lt;/code&gt; can observe writes from the previous critical section in the required order.&lt;/p&gt;

&lt;p&gt;Going further down, these guarantees are implemented through CPU Cache Coherence, x86 memory ordering, and the ordering constraints of the atomic instructions themselves.&lt;/p&gt;

&lt;p&gt;So Go maps back to the hardware model from the first article as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomicity
  → Atomic Instruction
  → Go / x86-64: LOCK CMPXCHGL / LOCK XADDL

Visibility
  → Cache Coherence
  → Typical x86-64 CPUs: MESI-family (e.g. MESIF / MOESI)

Ordering
  → Fence / equivalent ordering constraint
  → Go / x86-64: LOCK CMPXCHGL / LOCK XADDL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This amd64 Mutex path does not need an additional &lt;code&gt;MFENCE&lt;/code&gt;; the LOCKed RMW operations already provide the atomic update and ordering constraints required here.&lt;/p&gt;

&lt;p&gt;For implementation details, see &lt;a href="https://go.dev/src/internal/sync/mutex.go" rel="noopener noreferrer"&gt;&lt;code&gt;internal/sync/mutex.go&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://go.dev/src/runtime/sema.go" rel="noopener noreferrer"&gt;&lt;code&gt;runtime/sema.go&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://go.dev/src/internal/runtime/atomic/atomic_amd64.s" rel="noopener noreferrer"&gt;&lt;code&gt;internal/runtime/atomic/atomic_amd64.s&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. How Does CPython Implement threading.Lock?
&lt;/h2&gt;

&lt;p&gt;This section discusses current CPython only.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Layers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python API
Example: threading.Lock / acquire() / release()
Role: declare a mutual-exclusion boundary
        │
        ▼
CPython Binding (_thread)
Example: _thread.lock
Role: map the Python Lock API to CPython's lock implementation
        │
        ▼
CPython Implementation
Example: PyMutex
Role: implement Lock semantics
        │
        ▼
x86-64 Hardware
Example: Atomic Instruction / Cache Coherence / Fence
Role: provide the hardware foundation for Atomicity, Visibility, and Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 The Complete Path of One threading.Lock Operation
&lt;/h3&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%2Fn3xpcfn88qdq6shpp1kl.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%2Fn3xpcfn88qdq6shpp1kl.png" alt="How Python Lock acquire and release move through _thread, CPython PyMutex, OS Thread, and x86-64 Hardware for atomic competition, waiting, wakeup, and release." width="800" height="1103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sequence diagram above keeps only the main cross-layer path. CPython's internal contention path can be expanded further:&lt;/p&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%2Fs75zfob21iz0218dworw.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%2Fs75zfob21iz0218dworw.png" alt="acquire first tries the PyMutex fast path. On failure it may briefly spin when supported, then enters the Parking Lot. release either unlocks directly or wakes a waiting thread according to waiter state." width="800" height="928"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Atomicity
&lt;/h3&gt;

&lt;p&gt;Atomicity corresponds to the atomic state changes that &lt;code&gt;PyMutex&lt;/code&gt; performs on &lt;code&gt;_bits&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Acquire lock: _Py_atomic_compare_exchange_uint8(..., _Py_LOCKED)
Release lock (no waiter): _Py_atomic_compare_exchange_uint8(..., _Py_UNLOCKED)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When waiters exist, the release path enters &lt;code&gt;_PyParkingLot_Unpark()&lt;/code&gt;, whose callback atomically updates &lt;code&gt;_bits&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When multiple threads compete at the same time, only one can successfully set &lt;code&gt;_Py_LOCKED&lt;/code&gt; and enter the critical section.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Visibility and Ordering
&lt;/h3&gt;

&lt;p&gt;Visibility and Ordering correspond to the synchronization boundary formed by &lt;code&gt;acquire() / release()&lt;/code&gt;. In current CPython, the default atomic compare-exchange and store operations use &lt;code&gt;__ATOMIC_SEQ_CST&lt;/code&gt; in the GCC / Clang implementation.&lt;/p&gt;

&lt;p&gt;Going further down, these guarantees are implemented through CPU Cache Coherence, x86 memory ordering, and those sequentially consistent atomic operations.&lt;/p&gt;

&lt;p&gt;So current CPython maps back to the hardware model from the first article as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomicity
  → Atomic Instruction
  → CPython / x86-64: LOCK CMPXCHGB

Visibility
  → Cache Coherence
  → Typical x86-64 CPUs: MESI-family (e.g. MESIF / MOESI)

Ordering
  → Fence / equivalent ordering constraint
  → CPython / x86-64: SEQ_CST atomic
    (typically LOCK CMPXCHGB / memory XCHGB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This likewise does not require every Lock operation to execute an additional &lt;code&gt;MFENCE&lt;/code&gt;; on x86-64, LOCKed RMW and sequentially consistent atomic operations themselves provide the corresponding ordering constraints.&lt;/p&gt;

&lt;p&gt;For implementation details, see &lt;a href="https://github.com/python/cpython/blob/main/Modules/_threadmodule.c" rel="noopener noreferrer"&gt;&lt;code&gt;Modules/_threadmodule.c&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/python/cpython/blob/main/Python/lock.c" rel="noopener noreferrer"&gt;&lt;code&gt;Python/lock.c&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/python/cpython/blob/main/Python/parking_lot.c" rel="noopener noreferrer"&gt;&lt;code&gt;Python/parking_lot.c&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/python/cpython/blob/main/Include/cpython/pyatomic_gcc.h" rel="noopener noreferrer"&gt;&lt;code&gt;Include/cpython/pyatomic_gcc.h&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Common Pattern Across the Three Implementations
&lt;/h2&gt;

&lt;p&gt;After looking at Java, Go, and CPython, remove the implementation-specific details and focus only on the problems that a Mutex ultimately needs to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Who Gets In? — Atomically Modify Lock State
&lt;/h3&gt;

&lt;p&gt;Suppose a lock has only one internal state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 = unlocked
1 = locked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A and B must not both read &lt;code&gt;0&lt;/code&gt; first and then both write &lt;code&gt;1&lt;/code&gt;. Otherwise, both would believe they acquired the lock.&lt;/p&gt;

&lt;p&gt;Therefore, "check the lock state and change it to locked" must be an atomic operation, such as Compare-And-Swap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compare-And-Swap(lock_state, 0, 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When two execution units compete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU A                         CPU B

CAS 0 -&amp;gt; 1                   CAS 0 -&amp;gt; 1
    │                             │
    ▼                             ▼
  success                       failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only one competitor can successfully modify the lock state. A Mutex then uses this small hardware atomic operation to protect an arbitrary critical section such as &lt;code&gt;counter++&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 What Happens If You Don't Get the Lock? — Spin / Park / Wakeup
&lt;/h3&gt;

&lt;p&gt;After CAS fails, an execution unit cannot compete for the lock state forever.&lt;/p&gt;

&lt;p&gt;A common path is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try atomic lock acquisition
        │
        ├── success ──&amp;gt; enter critical section
        │
        └── failure
              │
              ├── Spin briefly
              │      │
              │      └── retry
              │
              └── Park / wait
                         │
                         └── wake after lock release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spin is suitable when the wait is expected to be very short because it avoids immediately entering a blocking path. If contention persists, Park avoids continuously occupying the CPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 After Acquiring the Lock, Why Can You See the Data Left by the Previous Holder?
&lt;/h3&gt;

&lt;p&gt;The first two questions answer "who gets the lock?" and "what happens if you don't get it?" The third common question is: why can writes performed by the previous lock holder inside the critical section be seen by the next lock holder?&lt;/p&gt;

&lt;p&gt;Across the three implementations, the key common model is that lock release and lock acquisition form a &lt;strong&gt;Release / Acquire&lt;/strong&gt; synchronization boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;previous lock holder
critical-section writes
    │
    ▼
unlock [Release]
    │
    ▼
lock [Acquire]
    │
    ▼
next lock holder
critical-section reads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Release constrains memory operations before the lock is released, while Acquire constrains memory operations after the lock is acquired.&lt;/p&gt;

&lt;p&gt;Therefore, when the next execution unit successfully acquires the same lock released by the previous execution unit, writes in the earlier critical section are ordered before reads in the later critical section. For a Mutex, this is the memory ordering that all three implementations need to establish.&lt;/p&gt;

&lt;p&gt;The first three sections already showed how each Runtime and CPU implementation realizes this Release / Acquire boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Next: Atomic
&lt;/h2&gt;

&lt;p&gt;The cleverness of a Mutex is that it only needs to maintain a very small atomic state to establish a mutual-exclusion boundary around a critical section of arbitrary size.&lt;/p&gt;

&lt;p&gt;Atomic narrows the protected scope further: instead of protecting an entire critical section, it directly guarantees that a single read, write, or update of a shared variable is indivisible.&lt;/p&gt;

&lt;p&gt;The next article continues with how this smaller synchronization unit is implemented.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.github.io/en/articles/concurrency-series-04-mutex-implementation/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>backend</category>
      <category>go</category>
      <category>java</category>
      <category>programming</category>
    </item>
    <item>
      <title>Concurrency Programming (3): Mutexes — Atomicity, Visibility, and Ordering at the Language Level</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Sun, 27 Sep 2026 12:17:25 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/concurrency-programming-3-mutexes-atomicity-visibility-and-ordering-at-the-language-level-1h3j</link>
      <guid>https://dev.to/thinkerqaq/concurrency-programming-3-mutexes-atomicity-visibility-and-ordering-at-the-language-level-1h3j</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;0. Continue from the Previous Article&lt;/li&gt;
&lt;li&gt;
1. What Guarantees Do the Lock Rules in Each Language Provide?

&lt;ul&gt;
&lt;li&gt;1.1 Java: synchronized and Monitors&lt;/li&gt;
&lt;li&gt;1.2 Go: sync.Mutex&lt;/li&gt;
&lt;li&gt;1.3 CPython: threading.Lock&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2. Next: How Is a Mutex Implemented?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  0. Continue from the Previous Article
&lt;/h2&gt;

&lt;p&gt;This article discusses Mutexes directly from the rules defined at the language memory model layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What Guarantees Do the Lock Rules in Each Language Provide?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Java: synchronized and Monitors
&lt;/h3&gt;

&lt;p&gt;Java's &lt;code&gt;synchronized&lt;/code&gt; locks the monitor associated with an object. &lt;a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-17.html#jls-17.1" rel="noopener noreferrer"&gt;JLS §17.1&lt;/a&gt; states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Only one thread at a time may hold a lock on a monitor.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This rule provides the exclusion needed for atomicity. At most one thread can hold the monitor, so only that thread may execute the protected critical section while competitors wait.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-17.html#jls-17.4.5" rel="noopener noreferrer"&gt;JLS §17.4.5&lt;/a&gt; also states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“An unlock on a monitor happens-before every subsequent lock on that monitor.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This rule provides both visibility and ordering.&lt;/p&gt;

&lt;p&gt;For visibility, A's writes are before its &lt;code&gt;unlock&lt;/code&gt;, while B's reads are after the subsequent &lt;code&gt;lock&lt;/code&gt;. Through happens-before transitivity, the writes in A's critical section become visible to B.&lt;/p&gt;

&lt;p&gt;For ordering, A's program order, the &lt;code&gt;unlock → lock&lt;/code&gt; relationship, and B's program order form one happens-before chain. B therefore cannot observe a result such as &lt;code&gt;ready = true, counter = 0&lt;/code&gt; when that would violate the established ordering.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Go: sync.Mutex
&lt;/h3&gt;

&lt;p&gt;Go's &lt;code&gt;sync.Mutex&lt;/code&gt; is not owned by a particular goroutine and is not reentrant. &lt;a href="https://pkg.go.dev/sync#Mutex.Lock" rel="noopener noreferrer"&gt;&lt;code&gt;sync.Mutex.Lock&lt;/code&gt;&lt;/a&gt; states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“If the lock is already in use, the calling goroutine blocks until the mutex is available.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This gives the exclusion needed for atomicity. While one goroutine holds the mutex, other goroutines attempting to acquire the same mutex wait rather than entering the critical section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://go.dev/ref/mem#Locks" rel="noopener noreferrer"&gt;The Go Memory Model - Locks&lt;/a&gt; further states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“For any &lt;code&gt;sync.Mutex&lt;/code&gt; or &lt;code&gt;sync.RWMutex&lt;/code&gt; variable &lt;code&gt;l&lt;/code&gt; and n &amp;lt; m, call n of &lt;code&gt;l.Unlock()&lt;/code&gt; is synchronized before call m of &lt;code&gt;l.Lock()&lt;/code&gt; returns.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This rule provides visibility and ordering.&lt;/p&gt;

&lt;p&gt;For visibility, A's writes are sequenced-before &lt;code&gt;Unlock&lt;/code&gt;; &lt;code&gt;Unlock&lt;/code&gt; is synchronized-before B's &lt;code&gt;Lock&lt;/code&gt; returns; B's reads are sequenced after that return. Together these relationships form happens-before, so A's writes are visible to B.&lt;/p&gt;

&lt;p&gt;For ordering, the same happens-before chain connects A's writes, &lt;code&gt;Unlock → Lock&lt;/code&gt;, and B's reads. B likewise cannot observe &lt;code&gt;ready = true, counter = 0&lt;/code&gt; when the synchronization relationship requires the earlier &lt;code&gt;counter&lt;/code&gt; write to be visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 CPython: threading.Lock
&lt;/h3&gt;

&lt;p&gt;Python's official &lt;a href="https://docs.python.org/3/library/threading.html#lock-objects" rel="noopener noreferrer"&gt;&lt;code&gt;threading.Lock&lt;/code&gt;&lt;/a&gt; documentation states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“A primitive lock is a synchronization primitive that is not owned by a particular thread when locked.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“All methods are executed atomically.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The locking semantics provide mutual exclusion: when the lock is already acquired, another thread's &lt;code&gt;acquire()&lt;/code&gt; waits until the lock becomes available, so the same lock serializes entry into the protected critical section.&lt;/p&gt;

&lt;p&gt;Visibility and ordering cannot be derived from a formal language-level memory-model rule in the same way as in Java or Go. Python does not define a general &lt;code&gt;release → acquire&lt;/code&gt; happens-before relationship comparable to those specifications; the documentation defines Lock as a synchronization primitive.&lt;/p&gt;

&lt;p&gt;Python programs should therefore use the same Lock to synchronize shared state rather than depending on the GIL or on whether a particular bytecode sequence happens to be interruptible in a particular implementation. How CPython implements the cross-critical-section synchronization boundary is an implementation question for the next article.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Next: How Is a Mutex Implemented?
&lt;/h2&gt;

&lt;p&gt;This article stops at the language level: it looks at what guarantees a Mutex provides to programmers from the perspectives of Atomicity, Visibility, and Ordering.&lt;/p&gt;

&lt;p&gt;The next article turns those language-level guarantees into implementation-level problems, following Java &lt;code&gt;synchronized&lt;/code&gt;, Go &lt;code&gt;sync.Mutex&lt;/code&gt;, and CPython &lt;code&gt;threading.Lock&lt;/code&gt; from the Runtime down to the CPU.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.github.io/en/articles/concurrency-series-03-mutex/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>go</category>
      <category>java</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Concurrency Programming (2): Language Memory Models — Rules Programmers Can Rely On</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Wed, 23 Sep 2026 14:01:10 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/concurrency-programming-2-language-memory-models-rules-programmers-can-rely-on-3m38</link>
      <guid>https://dev.to/thinkerqaq/concurrency-programming-2-language-memory-models-rules-programmers-can-rely-on-3m38</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;0. Continue from the Previous Article&lt;/li&gt;
&lt;li&gt;1. Why Does the Language Need a Memory Model Layer?&lt;/li&gt;
&lt;li&gt;
2. How Should Programmers Understand Memory Model Rules?

&lt;ul&gt;
&lt;li&gt;2.1 Java: Java Memory Model&lt;/li&gt;
&lt;li&gt;2.2 Go Memory Model&lt;/li&gt;
&lt;li&gt;2.3 CPython Concurrency Semantics&lt;/li&gt;
&lt;li&gt;2.3.1 GIL Mode&lt;/li&gt;
&lt;li&gt;2.3.2 Free-threaded Mode&lt;/li&gt;
&lt;li&gt;2.3.3 So What Should Python Programs Rely On?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3. Next: Mutexes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  0. Continue from the Previous Article
&lt;/h2&gt;

&lt;p&gt;This article shifts the perspective from hardware to the language memory model layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Why Does the Language Need a Memory Model Layer?
&lt;/h2&gt;

&lt;p&gt;Source code passes through a compiler and Runtime before it finally executes on a CPU. Compilers optimize code, and processors such as x86-64 and ARM64 do not allow exactly the same memory-access orderings.&lt;/p&gt;

&lt;p&gt;If programmers had to study every compiler, Runtime, and CPU separately, portable concurrent programming would be difficult to sustain.&lt;/p&gt;

&lt;p&gt;We therefore also need a language memory model layer. It defines the rules programmers need to understand, while the underlying Compiler / Runtime implements those rules differently for each platform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java                                  Go                                  Python
 │                                     │                                     │
 ▼                                     ▼                                     ▼
synchronized / volatile / AtomicInteger
                                       sync.Mutex / sync/atomic / channel
                                                                             threading.Lock / queue.Queue
 │                                     │                                     │
 ▼                                     ▼                                     ▼
Java Memory Model                Go Memory Model              CPython Concurrency Semantics
 │                                     │                                     │
 └─────────────────────────────────────┼─────────────────────────────────────┘
                                       │
                         Compiler / Runtime implementation
                         shields hardware-platform differences
                                       │
                              ┌────────┴────────┐
                              │                 │
                              ▼                 ▼
                           x86-64             ARM64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. How Should Programmers Understand Memory Model Rules?
&lt;/h2&gt;

&lt;p&gt;Through these three dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Atomicity&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visibility&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ordering&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In theory — only in theory — each concurrency utility has corresponding rules at the memory model layer that describe which of these properties it provides.&lt;/p&gt;

&lt;p&gt;This article will not go deeper yet. Instead, we will use the previous examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter = 1
ready = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the rules at the language memory model layer to establish:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Specifically&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Atomicity&lt;/td&gt;
&lt;td&gt;Why is &lt;code&gt;counter++&lt;/code&gt; not atomic?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visibility&lt;/td&gt;
&lt;td&gt;After A writes &lt;code&gt;counter = 1&lt;/code&gt;, why is B not guaranteed to observe &lt;code&gt;counter = 1&lt;/code&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordering&lt;/td&gt;
&lt;td&gt;When B observes &lt;code&gt;ready = true&lt;/code&gt;, why is B not guaranteed to observe the earlier &lt;code&gt;counter = 1&lt;/code&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  2.1 Java: Java Memory Model
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Atomicity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, the JMM does not have a rule that directly states that &lt;code&gt;counter++&lt;/code&gt; is not atomic, so we will explain it here.&lt;/p&gt;

&lt;p&gt;Semantically, &lt;code&gt;counter++&lt;/code&gt; is a compound operation. We can think of it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;read counter
     ↓
counter + 1
     ↓
write counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If two threads execute it concurrently:&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                    Thread B

counter++;                  counter++;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;these operations may interleave like this:&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                    Thread B

read counter = 0            read counter = 0

counter + 1                 counter + 1

write counter = 1           write counter = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both threads execute &lt;code&gt;counter++&lt;/code&gt;, yet the final result may still be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expected: counter = 2
actual:   counter = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, ordinary &lt;code&gt;counter++&lt;/code&gt; cannot be treated as atomic.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Visibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Thread A&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Thread B&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is:&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                    Thread B

counter = 1                 read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JLS §17.4.5 gives a very direct description of happens-before:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If one action happens-before another, then the first is visible to and ordered before the second.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, if we can establish:&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                    Thread B

counter = 1                 read counter
     │                           ▲
     │                           │
     └──── happens-before ───────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then Thread A's write is guaranteed to be visible to Thread B's read.&lt;/p&gt;

&lt;p&gt;But in the current code:&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                    Thread B

counter = 1                 read counter
     │                           ▲
     │                           │
     └── no happens-before ──────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;no such cross-thread happens-before relationship has been established.&lt;/p&gt;

&lt;p&gt;Therefore, Thread B is not guaranteed to observe &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Ordering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Return to the second example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Thread A&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Thread B&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is:&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                    Thread B

counter = 1
ready = true                read ready == true
                                  │
                                  ▼
                             read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within Thread A, &lt;code&gt;counter = 1&lt;/code&gt; comes before &lt;code&gt;ready = true&lt;/code&gt;; within Thread B, reading &lt;code&gt;ready&lt;/code&gt; comes before reading &lt;code&gt;counter&lt;/code&gt;. But the two threads are still missing a happens-before relationship from &lt;code&gt;ready = true&lt;/code&gt; to &lt;code&gt;read ready == true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the current code still does not establish a complete happens-before chain between Thread A and Thread B:&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                    Thread B

counter = 1
ready = true                read ready == true
     │                            │
     │                            ▼
     │                       read counter
     │
     └──── no cross-thread happens-before
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, even if B observes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ready == true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;B is still not guaranteed to observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter == 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2.2 Go Memory Model
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Atomicity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Go Memory Model provides a direct rule for this case:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If a write to a memory location occurs concurrently with another read or write, it constitutes a Data Race unless all of those accesses are atomic accesses provided by &lt;code&gt;sync/atomic&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But:&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="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is only an ordinary increment operation, not an atomic access provided by &lt;code&gt;sync/atomic&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Therefore, if two Goroutines concurrently execute:&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="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;they perform concurrent reads and writes on the same &lt;code&gt;counter&lt;/code&gt;, which constitutes a Data Race.&lt;/p&gt;

&lt;p&gt;So ordinary &lt;code&gt;counter++&lt;/code&gt; cannot be treated as an atomic concurrent update.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Visibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Continue with:&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;var&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="c"&gt;// Goroutine A&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="c"&gt;// Goroutine B&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Split into two columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine A                 Goroutine B

counter = 1                 read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly the kind of question the Go Memory Model starts from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Under what conditions can a read in one Goroutine be guaranteed to observe a write to the same variable in another Goroutine?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the formal rules, for an ordinary read to reliably observe a particular write, that write must be visible to the read; one of the conditions is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“w happens before r.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Applied to our example, if we can establish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine A                 Goroutine B

counter = 1                 read counter
     │                           ▲
     │                           │
     └──── happens-before ───────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the later read can reliably observe that write.&lt;/p&gt;

&lt;p&gt;But in the current code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine A                 Goroutine B

counter = 1                 read counter
     │                           ▲
     │                           │
     └── no happens-before ──────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;no such relationship has been established, and the program contains a Data Race.&lt;/p&gt;

&lt;p&gt;Therefore, the second Goroutine cannot rely on always observing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter == 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Ordering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now consider:&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="c"&gt;// Goroutine A&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;// Goroutine B&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&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;Split into two columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine A                 Goroutine B

counter = 1
ready = true                read ready == true
                                  │
                                  ▼
                             read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is essentially the same pattern used by the Go Memory Model under &lt;strong&gt;Incorrect synchronization&lt;/strong&gt;: one Goroutine writes data and then a flag; another Goroutine observes the flag and then reads the earlier data.&lt;/p&gt;

&lt;p&gt;The Go specification states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Even if this occurs, it does not imply that reads happening after r will observe writes that happened before w.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Applied to our example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine A                 Goroutine B

counter = 1
ready = true                read ready == true
                                  │
                                  ▼
                             read counter

        no happens-before guarantee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;observing ready == true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not imply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter must be observed as 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is very similar to the Java example.&lt;/p&gt;

&lt;p&gt;Both Java and Go use happens-before to describe important Visibility and Ordering guarantees.&lt;/p&gt;




&lt;h3&gt;
  
  
  2.3 CPython Concurrency Semantics
&lt;/h3&gt;

&lt;p&gt;Python differs from Java and Go in one important respect:&lt;/p&gt;

&lt;p&gt;Python does not define one unified concurrency memory model for all interpreter implementations at the same level as the JMM or Go Memory Model.&lt;/p&gt;

&lt;p&gt;So this article focuses on the most widely used implementation: &lt;strong&gt;CPython&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We will continue to use the same two examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and examine Atomicity, Visibility, and Ordering.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3.1 GIL Mode
&lt;/h4&gt;

&lt;p&gt;Before returning to &lt;code&gt;counter&lt;/code&gt;, first clarify one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What does the GIL actually protect?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The GIL is first and foremost a mechanism used by the CPython Runtime to protect Python objects and interpreter-internal state.&lt;/p&gt;

&lt;p&gt;The official Python C API documentation states the requirement directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“only a thread that holds the GIL may operate on Python objects or invoke Python’s C API.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, in default GIL-enabled CPython, a thread must hold the GIL before it can operate on Python objects or call the Python C API.&lt;/p&gt;

&lt;p&gt;The documentation then uses &lt;strong&gt;Reference Count&lt;/strong&gt; to explain why such a lock is needed: if two threads increment the same object's reference count at the same time, the result may be incremented only once instead of twice.&lt;/p&gt;

&lt;p&gt;Suppose a Python object currently has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;refcount = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If two threads could modify that reference count concurrently:&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                    Thread B

read refcount = 10          read refcount = 10

refcount + 1                refcount + 1

write refcount = 11         write refcount = 11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both threads increment the reference count once, yet the final result may be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expected: refcount = 12
actual:   refcount = 11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That would break CPython's management of Python object lifetimes.&lt;/p&gt;

&lt;p&gt;So the GIL primarily protects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             GIL
              │
              ▼
      CPython Runtime
              │
      ┌───────┴────────┐
      ▼                ▼
Python Objects     Runtime State
      │
      ├── Reference Count
      └── Object Internals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two different layers here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPython Runtime layer&lt;/strong&gt;: how to safely access and maintain Python objects under concurrency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application layer&lt;/strong&gt;: how to safely access and modify shared state under concurrency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The GIL primarily addresses the former. It does not make application code automatically thread-safe.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Atomicity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now return to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The official Python FAQ directly addresses this kind of operation.&lt;/p&gt;

&lt;p&gt;When discussing which operations are atomic in GIL-enabled CPython, it explicitly lists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as a non-atomic operation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“These aren’t: &lt;code&gt;i = i+1&lt;/code&gt;”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Therefore, even with the GIL, we cannot treat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as an atomic concurrent update as a whole.&lt;/p&gt;

&lt;p&gt;We need to distinguish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GIL
 │
 └── protection for CPython Runtime / Python Objects

counter += 1
 │
 └── a compound state update defined by the application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Visibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Continue with the same example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# Thread A
&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;# Thread B
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Split into two columns:&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                    Thread B

counter = 1                 read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the distinction between CPython and Java / Go appears.&lt;/p&gt;

&lt;p&gt;With Java and Go, we can continue to ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;write
 │
 │ happens-before ?
 ▼
read
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because those languages define formal Memory Models.&lt;/p&gt;

&lt;p&gt;Python, however, does not define a corresponding happens-before model that applies uniformly to all Python implementations.&lt;/p&gt;

&lt;p&gt;So we cannot draw the CPython code above as:&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                    Thread B

counter = 1                 read counter
     │                           ▲
     │                           │
     └──── Python happens-before ┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then derive a conclusion from some Python Language Memory Model rule, because there is no corresponding unified model of that kind.&lt;/p&gt;

&lt;p&gt;Conversely, because no separate rule specifies such a guarantee, we should treat this behavior as unspecified. If it is unspecified, then it does not provide a Visibility guarantee.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Ordering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finally, consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Thread A
&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="c1"&gt;# Thread B
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, split into two columns:&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                    Thread B

counter = 1
ready = True                read ready == True
                                  │
                                  ▼
                             read counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Likewise, there is no corresponding rule that specifies such a guarantee here, so we should treat this behavior as unspecified. If it is unspecified, then it does not provide an Ordering guarantee.&lt;/p&gt;




&lt;h4&gt;
  
  
  2.3.2 Free-threaded Mode
&lt;/h4&gt;

&lt;p&gt;Starting with Python 3.13, CPython provides free-threaded builds in which the GIL can be disabled.&lt;/p&gt;

&lt;p&gt;In GIL-enabled CPython:&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 ──┐
           │
           ├── GIL ───&amp;gt; Python Code
           │
Thread B ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In free-threaded CPython:&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 ─────────────&amp;gt; CPU Core 0

Thread B ─────────────&amp;gt; CPU Core 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple threads can execute Python code in parallel.&lt;/p&gt;

&lt;p&gt;But removing the GIL does not mean CPython no longer needs to protect its objects and Runtime state.&lt;/p&gt;

&lt;p&gt;Python's official free-threading documentation explicitly states that built-in types such as &lt;code&gt;dict&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, and &lt;code&gt;set&lt;/code&gt; use internal locks to protect against concurrent modification:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Built-in types like &lt;code&gt;dict&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, and &lt;code&gt;set&lt;/code&gt; use internal locks”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These internal locks are an implementation mechanism of free-threaded CPython. They should not be interpreted as Python the language defining a unified Memory Model.&lt;/p&gt;

&lt;p&gt;So the change is from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GIL-enabled

one global GIL
      │
      ▼
protect CPython Runtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Free-threaded

finer-grained internal synchronization
      │
      ▼
protect CPython Runtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For application code, the same three questions still exist.&lt;/p&gt;

&lt;p&gt;Different paths, same destination. Removing the GIL does not mean CPython no longer needs synchronization; it moves from one global lock toward finer-grained locks, atomic operations, and Runtime coordination. In this respect, its implementation approach increasingly resembles Java and Go.&lt;/p&gt;




&lt;h4&gt;
  
  
  2.3.3 So What Should Python Programs Rely On?
&lt;/h4&gt;

&lt;p&gt;In summary, Java, Go, and CPython differ at this layer in an important way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java
  │
  ▼
Java Memory Model
  │
  └── happens-before

Go
  │
  ▼
Go Memory Model
  │
  └── happens-before

Python
  │
  ▼
Concurrency semantics of different Interpreters
  │
  └── CPython
        ├── GIL-enabled
        └── Free-threaded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The summary is simple: Python does not have one unified Memory Model, at the same level as the JMM or Go Memory Model, that applies to every interpreter implementation. Therefore, when discussing concurrency safety in Python, first identify which Interpreter is being used, and rely on explicit synchronization tools and the concurrency guarantees they provide.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Next: Mutexes
&lt;/h2&gt;

&lt;p&gt;The article before last discussed the hardware layer; this article explains the rules at the language memory model layer.&lt;/p&gt;

&lt;p&gt;The next article begins with the first concrete synchronization tool, Mutex, and follows it from the language memory model layer all the way down to the hardware layer.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.github.io/en/articles/concurrency-series-02-language-memory-model/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>go</category>
      <category>java</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>What I Think About When I'm Anxious: Life and Planning</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Sat, 19 Sep 2026 03:06:39 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/what-i-think-about-when-im-anxious-life-and-planning-5eh5</link>
      <guid>https://dev.to/thinkerqaq/what-i-think-about-when-im-anxious-life-and-planning-5eh5</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;This article records some of my thoughts about life. It is not a rigorous theory of life, but simply an attempt to organize my own understanding.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was written in 2024 and reflects the stage of life I was in and how I thought at the time. When migrating it to this site, I made only minor adjustments to some wording.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Why Think About Life?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1. Instead of Being Consumed by Anxiety, Think About Meaningful Questions
&lt;/h3&gt;

&lt;p&gt;When I am under a lot of pressure, I tend to become anxious. Anxiety then makes me repeatedly think about many things that have not happened yet. Rather than being overwhelmed by those thoughts, I prefer to direct my attention toward questions that genuinely matter and write down what I think about them.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2. Approaching Thirty, It Is Time to Think About What It Means to "Stand Firm"
&lt;/h3&gt;

&lt;p&gt;As I approached the age traditionally associated with "standing firm," I began to seriously think about what I actually wanted to stand for. Confucius once said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At fifteen, I set my heart upon learning; at thirty, I stood firm; at forty, I was free from doubts; at fifty, I understood the decree of Heaven; at sixty, my ear was attuned; at seventy, I could follow my heart's desires without overstepping the bounds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was Confucius's summary of the stages of his own life. Here I am mainly interested in the phrase "at thirty, I stood firm": what exactly does "standing firm" mean?&lt;/p&gt;

&lt;p&gt;Different commentaries offer different interpretations. In his discussion in &lt;a href="https://zhwhdx.ustc.edu.cn/zhwhdx/news/detail_196888.htm" rel="noopener noreferrer"&gt;The Analects: Translation and Annotation&lt;/a&gt;, Yang Bojun interprets it as understanding ritual propriety and becoming confident in one's words and actions. His main evidence includes "Be inspired by the Songs, take your stand through ritual, and achieve completion through music" from the Analects, Tai Bo, and "If you do not study ritual, you have no way to establish yourself" from the Analects, Ji Shi. Under this interpretation, the "standing" Confucius described is closely related to ritual propriety.&lt;/p&gt;

&lt;p&gt;However, Confucius lived in the late Spring and Autumn period, when the feudal states were in conflict and society was unstable. His political ideal of restoring the rites of Zhou was difficult to realize in practice. Confucius traveled among different states in the hope of putting his political ideas into practice, but repeatedly encountered setbacks. The Records of the Grand Historian, in the "Hereditary House of Confucius," records an episode in which Duke Jing of Qi asked Confucius about government and considered employing him, but later abandoned the idea because of practical resistance:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Duke Jing asked Confucius about government. Confucius replied, "Let the ruler be a ruler, the minister a minister, the father a father, and the son a son." Duke Jing said, "Excellent! Indeed, if the ruler is not a ruler, the minister not a minister, the father not a father, and the son not a son, then even if there is grain, how could I get to eat it?" On another day he again asked Confucius about government, and Confucius replied, "Government lies in economizing expenditure." Duke Jing was pleased and intended to grant Confucius the fields of Nixi. Yan Ying remonstrated: "The Ru are eloquent and cannot be constrained by ordinary rules; they are arrogant and self-righteous and cannot be treated as subordinates; they exalt mourning, indulge grief, and impoverish families through lavish burials, so their customs cannot be adopted; they travel about persuading rulers and seeking support, so they cannot be used to govern a state. Since the great sages passed away and the Zhou royal house declined, rites and music have long been in disrepair. Now Confucius elaborates appearances and ornaments, complicated ceremonies of ascending and descending, and detailed rules of movement. Even generations would not exhaust his learning, nor could one master his rites within a lifetime. If you wish to use him to change the customs of Qi, this is not the way to put the common people first." After that, Duke Jing continued to receive Confucius respectfully but no longer asked him about ritual. On another occasion, Duke Jing detained Confucius and said, "I cannot support you as the Ji family does." He treated him at a level between the Ji and Meng families. Officials of Qi wanted to harm Confucius, and Confucius heard of it. Duke Jing said, "I am old; I can no longer employ you." Confucius therefore left and returned to Lu.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even so, Confucius continued to pursue his political ideals throughout his life. In my view, rather than interpreting "standing firm at thirty" as reaching a standardized social milestone, I prefer to understand it as gradually finding a direction that one is willing to invest in for the long term and beginning to take responsibility for that direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3. From Technical Planning to Life Planning
&lt;/h3&gt;

&lt;p&gt;Whenever I was asked about career planning in the past, I often gave a formulaic answer without much thought: reach a certain position after a certain number of years. At work, when discussing technical planning, it was also easy to reduce planning to completing a certain project by a certain date. In fact, I had never seriously asked why those goals mattered.&lt;/p&gt;

&lt;p&gt;Planning should not be only a timeline. If a technical system can be planned by considering its current state, goals, problems, and actions, can a life be examined seriously in a similar way? That became another reason for me to continue thinking about this topic.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4. Summary
&lt;/h3&gt;

&lt;p&gt;The rest of this article mainly discusses three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What is life?&lt;/li&gt;
&lt;li&gt;What factors influence life?&lt;/li&gt;
&lt;li&gt;How should we plan a life?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. What Is Life?
&lt;/h2&gt;

&lt;p&gt;Taken literally, a life is a person's lifetime. We can begin by discussing two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The person&lt;/li&gt;
&lt;li&gt;The lifetime&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3.1. The Person
&lt;/h3&gt;

&lt;p&gt;From an evolutionary perspective, humans and modern chimpanzees share a common ancestor and later evolved along different branches. Humans themselves are animals and have the same basic biological needs for eating, sleeping, and reproduction.&lt;/p&gt;

&lt;p&gt;So compared with other animals, what characteristics stand out in humans?&lt;/p&gt;

&lt;p&gt;In the Chinese intellectual tradition, Mencius famously discussed the distinction between humans and animals. In Mencius, Li Lou II, he wrote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The difference between humans and beasts is slight; ordinary people cast it away, while exemplary people preserve it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yang Bojun explains in his Mencius: Translation and Annotation that the difference between humans and animals is not great; ordinary people may discard it, while exemplary people preserve it. Following this line of thought, moral awareness is one important distinction between humans and other animals.&lt;/p&gt;

&lt;p&gt;In the Western intellectual tradition, Marx, in &lt;a href="https://www.marxists.org/chinese/marx/capital/05.htm" rel="noopener noreferrer"&gt;Capital&lt;/a&gt;, refers to Franklin's definition of humans as tool-making animals. The related discussion is commonly summarized as the idea that humans can make and use tools in labor and thereby transform nature.&lt;/p&gt;

&lt;p&gt;These judgments each have their own historical context and emphasize different aspects of humanity. Moral awareness, tool making, labor, language, and abstract thought all depend on human cognitive abilities. Rather than searching for one simple "essential difference," I am more interested in another fact: the human brain is plastic, and learning and experience continually change neural connections and patterns of activity. This means that throughout life, people can continue to learn, revise their understanding, and change their actions. As for whether I learned this in middle school or high school, those ancient neural connections have become a little difficult to recall.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2. A Lifetime
&lt;/h3&gt;

&lt;p&gt;As long as a person is alive, they continue to engage in different activities. Connect what a person does at different times and places, and those activities form the trajectory of their life and, to some extent, reflect their value choices.&lt;/p&gt;

&lt;p&gt;Take Su Dongpo as an example. &lt;a href="https://storymaps.com/stories/fe5123435ed84274a3a4e5a6723f007d" rel="noopener noreferrer"&gt;Exploring Su Shi's Life Trajectory Through Time and Space&lt;/a&gt; visualizes the geographic scope of his activities throughout his life.&lt;/p&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%2Ffe86rnraeegn4fcobath.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%2Ffe86rnraeegn4fcobath.png" alt="Map of Su Shi's life trajectory, marking many places where he lived and served" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visualization of Su Shi's life trajectory. Source: StoryMaps.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;He left behind a rich body of work in poetry, calligraphy, painting, and food culture. He also experienced repeated rises and falls in fortune, yet maintained a relatively open-minded attitude toward life. As he wrote in "Calming the Waves":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not mind the sound of rain beating through the forest; why not chant and walk on at ease?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whatever a person's achievements, their time is always limited. The activities to which a person devotes that time, and how they distribute it among those activities, ultimately make up the life they experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3. Summary
&lt;/h3&gt;

&lt;p&gt;For now, I would summarize my understanding of life this way: human time is limited, but people can continually adjust their understanding through learning and reflection, and based on that understanding, choose how to use their time. Those choices cannot completely determine a life, but they can shape it to some extent.&lt;/p&gt;

&lt;p&gt;I say only "to some extent" because life is also affected by many factors that individuals cannot decide for themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What Factors Influence Life?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1. Starting with "Fate"
&lt;/h3&gt;

&lt;p&gt;My parents told me that when I was born, my family chose my name partly according to the traditional Five Elements. As the saying goes, you make up for whatever is lacking, so my name ended up containing a character associated with "earth." I do not believe that traditional fortune-telling can determine a person's life, but this small story raises a question worth taking seriously: which parts of life come from inborn conditions, which come from the external environment, and which can be changed through our own choices?&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2. Genes and Environment
&lt;/h3&gt;

&lt;p&gt;A person develops from a fertilized egg. The chromosomes in that fertilized egg carry genetic information, and genes in DNA are basic units of heredity.&lt;/p&gt;

&lt;p&gt;Genes influence many human traits, but complex characteristics such as height, appearance, personality, and health are usually not determined directly by a single gene. They are often shaped by the combined effects of many genes, the environment in which a person grows up, lifestyle, and chance events. Even identical twins with highly similar genetic information can develop different personalities, habits, and values when raised in different environments.&lt;/p&gt;

&lt;p&gt;Returning to the subject of life: inborn conditions cannot be chosen by the individual, and accidents and external circumstances cannot be fully controlled. But there is still some room to adjust the learning environment, lifestyle, information one is exposed to, and concrete actions one takes. These adjustable factors influence a person's understanding and choices, which in turn influence the trajectory of their life.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3. Summary
&lt;/h3&gt;

&lt;p&gt;Life is influenced by both inborn conditions and the external environment, while also containing parts that individuals can choose and adjust. Together, these factors create the different lives that people experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. How to Plan a Life
&lt;/h2&gt;

&lt;p&gt;Life planning is an enormous topic. Here I only want to briefly summarize how I think about it.&lt;/p&gt;

&lt;p&gt;First, there is little value in spending too much energy on inborn conditions that cannot be changed or accidents that cannot be predicted. Instead, identify the parts that you can influence. Second, improve your understanding through learning and practice, identify the categories of activity that genuinely matter at your current stage of life, then allocate time, take action, and review the results regularly based on your current goals.&lt;/p&gt;

&lt;p&gt;For me, the activities worth paying attention to over the long term mainly include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learning: continuously improve my understanding and abilities&lt;/li&gt;
&lt;li&gt;Health: maintain the foundation that supports other activities&lt;/li&gt;
&lt;li&gt;Love: learn to build and maintain important relationships&lt;/li&gt;
&lt;li&gt;Work: it occupies a substantial part of life and allows people to sustain themselves, take responsibility, and create value&lt;/li&gt;
&lt;li&gt;Recreation: preserve room for rest, interests, and a sense of ease in life&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This list is not a fixed answer. As life stages and real-world conditions change, the priorities among these activities also need to be reassessed. The purpose of planning is not to control every outcome, but to use limited time more consciously under uncertainty.&lt;/p&gt;




&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.github.io/en/articles/how-i-think-about-life-when-anxious/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>Why count++ Breaks Under Concurrency: Atomicity, Visibility &amp; Ordering — Concurrency Programming (1)</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Fri, 18 Sep 2026 06:37:56 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/concurrency-programming-1-start-with-the-hardware-from-count-to-atomicity-visibility-and-1a0o</link>
      <guid>https://dev.to/thinkerqaq/concurrency-programming-1-start-with-the-hardware-from-count-to-atomicity-visibility-and-1a0o</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. Start with the von Neumann Architecture&lt;/li&gt;
&lt;li&gt;2. What Does &lt;code&gt;count++&lt;/code&gt; Become?&lt;/li&gt;
&lt;li&gt;3. Why Do We Need Caches?&lt;/li&gt;
&lt;li&gt;4. Concurrency on One Core: Why Can &lt;code&gt;count++&lt;/code&gt; Lose an Update?&lt;/li&gt;
&lt;li&gt;5. From One Core to Multiple Cores: Visibility&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6. What About the Order of Multiple Memory Operations?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6.1 Store Buffer&lt;/li&gt;
&lt;li&gt;6.2 Out-of-Order Execution&lt;/li&gt;
&lt;li&gt;6.3 Store Buffer vs Out-of-Order Execution&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;7. We Have Actually Encountered Three Problems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;8. How Does Hardware Address Them?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8.1 Atomicity: Atomic Instructions&lt;/li&gt;
&lt;li&gt;8.2 Visibility: Cache Coherence&lt;/li&gt;
&lt;li&gt;8.3 Memory Ordering: Fence&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;9. Summary&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This article continues to use &lt;code&gt;count&lt;/code&gt;, but focuses only on the hardware layer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What concurrency problems do the CPU and memory system introduce, and what capabilities does the hardware provide to address them?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Start with the von Neumann Architecture
&lt;/h2&gt;

&lt;p&gt;The von Neumann model treats programs as data: instructions and data are stored in the same general way. At a high level, a computer can be divided into a control unit, arithmetic unit, memory, input devices, and output devices.&lt;/p&gt;

&lt;p&gt;The arithmetic and control units form the CPU, which also contains registers. The &lt;strong&gt;Program Counter (PC) stores the address of the next instruction&lt;/strong&gt;; the Instruction Register (IR) stores the instruction currently being processed; general-purpose registers such as &lt;code&gt;R1&lt;/code&gt; hold data and intermediate results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────┐                    ┌──────────────────┐
│   Input Device   │                    │  Output Device   │
└─────────┬────────┘                    └─────────▲────────┘
          │                                       │
          └───────────────────┬───────────────────┘
                              │
                          System Bus
                              │
             ┌────────────────┴─────────────────┐
             │                                  │
             ▼                                  ▼
┌────────────────────────┐         ┌────────────────────────┐
│          CPU           │         │         Memory         │
│                        │         │                        │
│      Control Unit      │         │  Instructions + Data   │
│     Arithmetic Unit    │         │                        │
│       Registers        │         │                        │
│       PC / IR / R1     │         │                        │
└────────────────────────┘         └────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CPU execution loop can be simplified to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PC provides instruction address
        ↓
Fetch → Decode → Execute
        ↓
PC advances to the next instruction and repeats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CPU repeatedly performs &lt;code&gt;Fetch → Decode → Execute&lt;/code&gt;. A source-level expression such as &lt;code&gt;count++&lt;/code&gt; must eventually be converted into machine instructions that follow this process.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. What Does &lt;code&gt;count++&lt;/code&gt; Become?
&lt;/h2&gt;

&lt;p&gt;For discussion, we can simplify &lt;code&gt;count++&lt;/code&gt; into three machine-level operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOAD  R1, [count]    // load count into R1
ADD   R1, 1          // increment R1 inside the CPU
STORE [count], R1    // write the result back to count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;LOAD&lt;/code&gt; and &lt;code&gt;STORE&lt;/code&gt; access data. That leads naturally to the next question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What would happen if every LOAD and STORE had to wait directly for main memory?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Why Do We Need Caches?
&lt;/h2&gt;

&lt;p&gt;A CPU executes far faster than main memory can be accessed. If every &lt;code&gt;LOAD&lt;/code&gt; had to wait for main memory to return data and every &lt;code&gt;STORE&lt;/code&gt; had to wait until memory completed the write, the CPU would spend a large amount of time idle.&lt;/p&gt;

&lt;p&gt;Modern processors therefore place smaller, faster cache levels between a CPU core and main memory. A simplified hierarchy looks like this; exact levels and which caches are shared depend on the processor design:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU
 │
 ▼
L1 Cache
 │
 ▼
L2 Cache
 │
 ▼
L3 Cache
 │
 ▼
Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the CPU accesses &lt;code&gt;count&lt;/code&gt;, it normally loads the cache line containing it into a cache. Simplifying again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU
 │
 ▼
Cache: count = 0
 │
 ▼
Memory: count = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later accesses to &lt;code&gt;count&lt;/code&gt; may hit in the cache instead of going to main memory every time.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caches address the speed gap between the CPU and main memory.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a performance optimization. It does not make:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a safe concurrent operation automatically.&lt;/p&gt;

&lt;p&gt;Start with a single core.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Concurrency on One Core: Why Can &lt;code&gt;count++&lt;/code&gt; Lose an Update?
&lt;/h2&gt;

&lt;p&gt;Suppose the machine has only one CPU core:&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 ──┐
           │
           ├──&amp;gt; Core 0 ──&amp;gt; Cache ──&amp;gt; Memory
           │
Thread B ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two threads cannot literally execute at the same instant on that core, but the scheduler can interleave them.&lt;/p&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the following schedule is possible:&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                              Thread B
   │                                    │
   ├─ LOAD count -&amp;gt; 0                   │
   │                                    │
   ├─────── context switch ────────────&amp;gt;│
   │                                    ├─ LOAD  count -&amp;gt; 0
   │                                    ├─ ADD   1
   │                                    ├─ STORE count -&amp;gt; 1
   │                                    │
   │&amp;lt;────── context switch ─────────────┤
   ├─ ADD   1                           │
   ├─ STORE count -&amp;gt; 1                  │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During a context switch, the operating system preserves Thread A's execution context, including intermediate state. When A resumes, it can continue computing from the &lt;code&gt;0&lt;/code&gt; it read earlier.&lt;/p&gt;

&lt;p&gt;The final result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason is that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOAD
ADD
STORE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;are not one indivisible operation.&lt;/p&gt;

&lt;p&gt;If another execution unit can run between those steps, a lost update becomes possible.&lt;/p&gt;

&lt;p&gt;This gives us the first problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Problem 1: How can a compound operation execute indivisibly?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the problem of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Atomicity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will not answer it yet. First, move from one core to multiple cores.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. From One Core to Multiple Cores: Visibility
&lt;/h2&gt;

&lt;p&gt;With multiple CPU cores, two threads may truly execute in parallel:&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                       Thread B
   │                              │
   ▼                              ▼
Core A                         Core B
   │                              │
   ▼                              ▼
Cache A                        Cache B
   │                              │
   └──────────────┬───────────────┘
                  ▼
                Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original &lt;code&gt;count++&lt;/code&gt; race still exists, but multiple cores introduce another issue.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both Core A and Core B have read &lt;code&gt;count&lt;/code&gt;. The same data may now exist in separate caches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Core A Cache                    Core B Cache

count = 0                       count = 0

               Memory
              count = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Core A changes &lt;code&gt;count&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Core A Cache                    Core B Cache

count = 1                       count = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The question becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;May Core B keep using its old copy?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or more generally:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Problem 2: After one core writes data, when can other cores observe the new value?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visibility
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, hold the answer for a moment and move to the third class of problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. What About the Order of Multiple Memory Operations?
&lt;/h2&gt;

&lt;p&gt;Continue with a publication-style example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 0
ready = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread A                         Thread B
   │                                │
   ├─ count = 1                     ├─ read ready
   └─ ready = true                  └─ if true, read count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A programmer naturally wants the following guarantee:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If Thread B has already observed &lt;code&gt;ready = true&lt;/code&gt;, it should also observe the earlier write &lt;code&gt;count = 1&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ideally, B should only see one of these outcomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outcome 1:

Thread A                         Thread B

                                 read ready -&amp;gt; false
                                 do not enter if

Outcome 2:

Thread A                         Thread B

count = 1
ready = true                     read ready -&amp;gt; true
                                 enter if
                                 read count -&amp;gt; 1
                                 print 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do not want:&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                         Thread B

count = 1
(write not yet observed by B)
ready = true                     read ready -&amp;gt; true
                                 enter if
                                 read count -&amp;gt; 0
                                 print 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Once B has read &lt;code&gt;ready = true&lt;/code&gt;, how do we prevent it from still reading the old &lt;code&gt;count = 0&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is no longer merely a question about multiple cached copies of the same location. We now have two different memory locations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count
ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a relationship between two writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 1
ready = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us the third problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Problem 3: In what order may memory operations become observable to other cores?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory Ordering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does hardware make this complicated? Primarily because modern CPUs introduce aggressive performance optimizations.&lt;/p&gt;




&lt;h3&gt;
  
  
  6.1 Store Buffer
&lt;/h3&gt;

&lt;p&gt;Continue with &lt;code&gt;count++&lt;/code&gt;. Suppose &lt;code&gt;count = 0&lt;/code&gt; and Core A executes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOAD  count -&amp;gt; 0
ADD   1     -&amp;gt; 1
STORE [count], 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;STORE&lt;/code&gt; of &lt;code&gt;count = 1&lt;/code&gt; may first enter a store buffer.&lt;/p&gt;

&lt;p&gt;First, place the Store Buffer back into a slightly more complete conceptual write path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU (Core)
    │
    ▼
Store Buffer
    │
    ▼
Cache
    │
    ▼
Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note: the Store Buffer and Cache are not the same thing.&lt;/strong&gt; The Store Buffer is a temporary structure on a core's write path; the Cache stores cached data. Real processors have more complicated write paths, cache hierarchies, and coherence protocols. This diagram is only meant to distinguish the two layers.&lt;/p&gt;

&lt;p&gt;To highlight the point that “the write has already been executed by the current Core, but another Core has not observed it yet,” the following diagram omits Cache / Coherence details and continues with a simplified view:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU (Core A)
executes STORE [count], 1
        │
        ▼
Store Buffer
holds count = 1 temporarily
        │
        ▼
Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Core A can continue executing before the buffered write becomes visible to other cores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initial: count = 0

Core A                               Core B
   │                                    │
   ├─ LOAD  count -&amp;gt; 0                  │
   ├─ ADD   1     -&amp;gt; 1                  │
   ├─ STORE count -&amp;gt; 1                  │
   │  enters Store Buffer               │
   ├─ continue with later instructions  │
   │                                    ├─ LOAD count -&amp;gt; 0
   │                                    │  still sees old value
   └─ count = 1 becomes visible         │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  6.2 Out-of-Order Execution
&lt;/h3&gt;

&lt;p&gt;Modern CPUs also rearrange internal execution, where dependencies allow it, to keep execution units busy.&lt;/p&gt;

&lt;p&gt;As long as this does not change the result observed by the current thread, such optimization is valid from that thread's perspective. The difficulty appears when multiple cores interact.&lt;/p&gt;

&lt;p&gt;Return to the publication example:&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 / Core A                 Thread B / Core B

count = 1                        if ready {
ready = true                         print(count)
                                 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source code writes &lt;code&gt;count&lt;/code&gt; before &lt;code&gt;ready&lt;/code&gt;. But without additional ordering constraints, on hardware whose memory model permits it, another core may observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initial: count = 0, ready = false

Thread A / Core A                 Thread B / Core B
   │                                  │
   ├─ STORE count = 1                 │
   │  not yet observed by Core B      │
   ├─ STORE ready = true              │
   │                                  ├─ LOAD ready -&amp;gt; true
   │                                  └─ LOAD count -&amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, A issued the &lt;code&gt;count&lt;/code&gt; write first in source order, yet B observed &lt;code&gt;ready = true&lt;/code&gt; first and then still read the old &lt;code&gt;count = 0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The underlying question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In what order is another core allowed to observe these memory operations?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  6.3 Store Buffer vs Out-of-Order Execution
&lt;/h3&gt;

&lt;p&gt;These two mechanisms are easy to confuse, so compare the questions they answer directly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What question does it answer?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Store Buffer&lt;/td&gt;
&lt;td&gt;After a &lt;code&gt;STORE&lt;/code&gt; has already been executed by the current Core, why might another Core still not see it yet?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Out-of-Order Execution&lt;/td&gt;
&lt;td&gt;Why might an independent instruction that appears later in source code execute earlier inside the Core?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Therefore, Store Buffer and Out-of-Order Execution are not the same mechanism: the former concerns write propagation and visibility timing, while the latter concerns instruction scheduling inside the Core. Both can affect the memory order eventually observed by other Cores.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. We Have Actually Encountered Three Problems
&lt;/h2&gt;

&lt;p&gt;The earlier examples can be summarized as three categories:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;What hardware must define&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Atomicity&lt;/td&gt;
&lt;td&gt;A and B both execute &lt;code&gt;count++&lt;/code&gt;, both can read &lt;code&gt;0&lt;/code&gt;, and both finally write &lt;code&gt;1&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Which operations are atomic?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visibility&lt;/td&gt;
&lt;td&gt;Core A has changed &lt;code&gt;count&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;, while Core B may still read a cached &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;When does a write by one core become observable to another?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordering&lt;/td&gt;
&lt;td&gt;Core A writes &lt;code&gt;count = 1&lt;/code&gt; and then &lt;code&gt;ready = true&lt;/code&gt;, while Core B may observe &lt;code&gt;ready = true&lt;/code&gt; and still read &lt;code&gt;count = 0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Which ordering relationships are guaranteed between memory operations?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first problem can occur both with single-core context switches and with true multicore parallelism. The key issue is not the number of CPU cores, but whether the steps of a Read-Modify-Write sequence can be interleaved with another execution unit.&lt;/p&gt;

&lt;p&gt;Now we can ask what primitives the hardware provides:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What mechanisms does modern hardware provide for these three problems?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  8. How Does Hardware Address Them?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  8.1 Atomicity: Atomic Instructions
&lt;/h3&gt;

&lt;p&gt;A normal &lt;code&gt;LOAD + ADD + STORE&lt;/code&gt; consists of several operations. To make a Read-Modify-Write operation appear indivisible to other execution units, hardware provides atomic operations 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;Compare-And-Swap
Exchange
Fetch-And-Add
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, these operations are not necessarily a single microscopic step. “Atomic” describes what competing execution units are allowed to observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────┐
│ read old value   │
│      │           │
│      ▼           │
│ modify           │
│      │           │
│      ▼           │
│ write new value  │
└────────┬─────────┘
         │
         └── appears as one indivisible atomic operation to competitors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return to &lt;code&gt;count++&lt;/code&gt;. If a language implements the increment with a hardware-supported atomic Read-Modify-Write, we can reason about the result as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initial: count = 0

Thread A                         Thread B

atomically change count 0 -&amp;gt; 1
                                 atomically change count 1 -&amp;gt; 2

final: count = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not matter whether A or B wins first. Each atomic update must operate on a definite previous value, so both threads cannot independently read &lt;code&gt;0&lt;/code&gt; and then both write &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  8.2 Visibility: Cache Coherence
&lt;/h3&gt;

&lt;p&gt;Multicore processors use cache-coherence protocols to coordinate cached copies of the same memory location across cores.&lt;/p&gt;

&lt;p&gt;MESI is one of the classic coherence protocols:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;M - Modified
E - Exclusive
S - Shared
I - Invalid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real processors may use MESI or extensions and variants 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;MESI
MOESI
MESIF
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do not need every state transition here; focus on the problem coherence solves.&lt;/p&gt;

&lt;p&gt;Suppose both cores cache the line containing &lt;code&gt;count&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Core A Cache              Core B Cache

count = 0                 count = 0
Shared                    Shared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Core A wants to modify it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the hardware must coordinate ownership of that cache line and the state of other copies.&lt;/p&gt;

&lt;p&gt;At a very high level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Core A wants to modify count
        │
        ▼
obtain write permission for the cache line
        │
        ▼
invalidate copies that may no longer be used
        │
        ▼
Core A performs the modification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After Core A obtains write permission, Core B's old &lt;code&gt;count = 0&lt;/code&gt; copy is invalidated. The next time B reads &lt;code&gt;count&lt;/code&gt;, it cannot continue using that stale copy.&lt;/p&gt;




&lt;h3&gt;
  
  
  8.3 Memory Ordering: Fence
&lt;/h3&gt;

&lt;p&gt;Consider again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 1
ready = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modern CPUs use store buffers, out-of-order execution, and other mechanisms to improve performance. A hardware memory model must define:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which memory-operation orderings are guaranteed?
Which reorderings are permitted?
Which outcomes may different cores observe?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is memory ordering.&lt;/p&gt;

&lt;p&gt;Return to the publication example. If &lt;code&gt;count = 1&lt;/code&gt; has not yet become observable to B, hardware that permits the outcome might allow:&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 / Core A                       Thread B / Core B
|                                       |
+-- STORE count = 1                     |
+-- STORE ready = true                  |
|                                       +-- LOAD ready -&amp;gt; true
|                                       +-- LOAD count -&amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To forbid that result, fences can constrain the order of memory operations on each side:&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 / Core A                       Thread B / Core B
|                                       |
+-- STORE count = 1                     |
+-- FENCE                               |
+-- STORE ready = true                  |
|                                       +-- LOAD ready -&amp;gt; true
|                                       +-- FENCE
|                                       +-- LOAD count -&amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The writer-side fence prevents &lt;code&gt;count = 1&lt;/code&gt; from being ordered after &lt;code&gt;ready = true&lt;/code&gt;. The reader-side fence prevents the read of &lt;code&gt;count&lt;/code&gt; from being ordered before the read of &lt;code&gt;ready&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A fence does not guarantee that B will read &lt;code&gt;ready = true&lt;/code&gt;. But once B has observed &lt;code&gt;ready = true&lt;/code&gt; under the required synchronization pattern, the later read of &lt;code&gt;count&lt;/code&gt; cannot still return the stale &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Summary
&lt;/h2&gt;

&lt;p&gt;Hardware provides different foundations for the three concurrency problems:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Hardware capability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Atomicity&lt;/td&gt;
&lt;td&gt;Atomic Instruction: provides an atomic Read-Modify-Write operation (for example, Fetch-And-Add), so one update appears indivisible to competitors and they cannot observe an intermediate state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visibility&lt;/td&gt;
&lt;td&gt;Cache Coherence: coordinates the state of copies of the same cache line across cores, so writes propagate according to the coherence protocol and other cores cannot continue using an invalidated stale copy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordering&lt;/td&gt;
&lt;td&gt;Fence: constrains related Loads / Stores from being observably reordered across the barrier, establishing the memory-operation ordering relationships required across cores.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The next article returns to the language layer and looks at how Java, Go, and CPython turn these hardware capabilities into rules that programmers can rely on.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.github.io/en/articles/concurrency-series-01-hardware/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>programming</category>
      <category>computerscience</category>
      <category>performance</category>
      <category>java</category>
    </item>
    <item>
      <title>Where Concurrency Problems Begin: Shared State, Message Passing, and Semantics — Concurrency Programming (0)</title>
      <dc:creator>ThinkerQAQ</dc:creator>
      <pubDate>Tue, 15 Sep 2026 12:10:13 +0000</pubDate>
      <link>https://dev.to/thinkerqaq/concurrency-programming-0-the-problem-space-and-scope-1ig5</link>
      <guid>https://dev.to/thinkerqaq/concurrency-programming-0-the-problem-space-and-scope-1ig5</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. Define the Scope First&lt;/li&gt;
&lt;li&gt;2. Start with a Shared Variable&lt;/li&gt;
&lt;li&gt;
3. Two Main Coordination Models

&lt;ul&gt;
&lt;li&gt;3.1 Shared Memory: Protect Shared State with Synchronization&lt;/li&gt;
&lt;li&gt;3.2 Message Passing: Coordinate through Messages&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4. Why Does This Code Work Correctly?&lt;/li&gt;
&lt;li&gt;5. Languages Need to Define Concurrency Semantics&lt;/li&gt;
&lt;li&gt;6. Why Do We Still Need to Go Down to the Hardware?&lt;/li&gt;
&lt;li&gt;7. Next: Start with the Hardware&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Define the Scope First
&lt;/h2&gt;

&lt;p&gt;This series discusses only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Concurrency within a single process on a single machine.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, the focus is on multiple execution units inside the same process.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java Thread / Virtual Thread;&lt;/li&gt;
&lt;li&gt;Go Goroutine;&lt;/li&gt;
&lt;li&gt;Python Thread / asyncio Task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inter-process communication, distributed systems, and network communication are outside the scope.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Start with a Shared Variable
&lt;/h2&gt;

&lt;p&gt;Suppose a process contains this variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now there are two execution units:&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                    Thread B

count++                     count++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If A and B each execute once, is the final result guaranteed to be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;count++&lt;/code&gt; can be logically broken down into three steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;read count
compute count + 1
write count back
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the following interleaving is possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initial: count = 0

Thread A                    Thread B

read count -&amp;gt; 0
compute count + 1 -&amp;gt; 1
                            read count -&amp;gt; 0
                            compute count + 1 -&amp;gt; 1
write count -&amp;gt; 1
                            write count -&amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final value becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both threads complete a &lt;code&gt;+1&lt;/code&gt;, but because they read the same old value &lt;code&gt;0&lt;/code&gt;, both compute the result as &lt;code&gt;1&lt;/code&gt;, and one update is overwritten.&lt;/p&gt;

&lt;p&gt;The key problem is that two threads modify the same &lt;code&gt;count&lt;/code&gt; state concurrently:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Multiple execution units access and modify the same state at the same time.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Within one process on one machine, execution units mainly coordinate around shared data in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared Memory / Shared State&lt;/strong&gt;: multiple execution units directly access the same state;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message Passing&lt;/strong&gt;: execution units exchange information through messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can use the same &lt;code&gt;count++&lt;/code&gt; example to see how each model handles the problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Two Main Coordination Models
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Shared Memory: Protect Shared State with Synchronization
&lt;/h3&gt;

&lt;p&gt;With shared memory, multiple execution units directly access the same state. Continue with &lt;code&gt;count&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Java, the shared state can be protected with synchronization, for example &lt;code&gt;synchronized&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;synchronized&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both threads still access the same &lt;code&gt;Counter&lt;/code&gt;:&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                    Thread B

counter.increment()         counter.increment()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But because of &lt;code&gt;synchronized&lt;/code&gt;, the two threads cannot enter the critical section in &lt;code&gt;increment()&lt;/code&gt; at the same time.&lt;/p&gt;

&lt;p&gt;Suppose Thread A acquires the lock first. The execution becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initial: count = 0

Thread A                         Thread B

request to enter increment()
acquire lock
                                 request to enter increment()
                                 cannot acquire the same lock; wait
read count       -&amp;gt; 0
compute count+1  -&amp;gt; 1
write count      -&amp;gt; 1
leave critical section and release lock
                                 acquire lock
                                 read count       -&amp;gt; 1
                                 compute count+1  -&amp;gt; 2
                                 write count      -&amp;gt; 2
                                 leave critical section and release lock

final: count = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Thread B acquires the lock first, the order is reversed but the result is still &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What matters is not which thread runs first. What matters is that another thread cannot enter the same critical section before one complete &lt;code&gt;read → compute → write&lt;/code&gt; sequence finishes.&lt;/p&gt;

&lt;p&gt;The interleaving in which both threads read &lt;code&gt;0&lt;/code&gt; therefore no longer occurs.&lt;/p&gt;

&lt;p&gt;Go and Python provide similar mechanisms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go      -&amp;gt; Mutex
Python  -&amp;gt; Lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shared idea is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The state is still shared, but access to that state is coordinated through synchronization.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  3.2 Message Passing: Coordinate through Messages
&lt;/h3&gt;

&lt;p&gt;Another approach is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Execution units exchange messages instead of having multiple execution units directly modify the same state.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Continue with &lt;code&gt;count&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, in Go:&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="n"&gt;increments&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// Counter Owner Goroutine&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;increments&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;delta&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;The two producer goroutines do not modify &lt;code&gt;count&lt;/code&gt; directly. They send increments to the same channel:&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="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// Goroutine A&lt;/span&gt;
    &lt;span class="n"&gt;increments&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// Goroutine B&lt;/span&gt;
    &lt;span class="n"&gt;increments&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can abstract it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goroutine A ── +1 ──┐
                     ├──&amp;gt; Channel ──&amp;gt; Counter Owner Goroutine ──&amp;gt; count++
Goroutine B ── +1 ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both goroutines only send messages. The actual &lt;code&gt;count&lt;/code&gt; is modified by a dedicated &lt;code&gt;Counter Owner Goroutine&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Java and Python have similar message-passing tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java &lt;code&gt;BlockingQueue&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Python &lt;code&gt;queue.Queue&lt;/code&gt; / &lt;code&gt;asyncio.Queue&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The shared idea is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Execution units coordinate through messages instead of directly modifying the same state concurrently.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. Why Does This Code Work Correctly?
&lt;/h2&gt;

&lt;p&gt;So far, we have handled the original &lt;code&gt;count++&lt;/code&gt; problem in two ways.&lt;/p&gt;

&lt;p&gt;With shared memory, we used synchronization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lock / synchronized / Mutex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With message passing, we used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue / Channel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is a more fundamental question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why does this code work correctly?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To answer this question, we need to discuss concurrency from three fundamental perspectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Atomicity&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visibility&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ordering&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Return to the original &lt;code&gt;count++&lt;/code&gt; example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Atomicity — which operations are atomic?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;count++&lt;/code&gt; includes a read, an increment, and a write. After A acquires the lock, why must B wait until A changes &lt;code&gt;count&lt;/code&gt; from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; and releases the lock before entering the same critical section?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visibility — when does a write become visible?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
After A writes &lt;code&gt;count = 1&lt;/code&gt; and releases the lock, why must B read &lt;code&gt;1&lt;/code&gt; after acquiring the same lock instead of continuing to see the old value &lt;code&gt;0&lt;/code&gt;? With a channel, why can the Counter Owner receive the &lt;code&gt;+1&lt;/code&gt; sent by a producer?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ordering — which operations have an ordering relationship?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Why must A's &lt;code&gt;write count = 1 → release lock&lt;/code&gt; happen before B's &lt;code&gt;acquire lock → read count = 1&lt;/code&gt;? With a channel, why must a &lt;code&gt;send +1&lt;/code&gt; happen before the corresponding &lt;code&gt;receive +1 → update count&lt;/code&gt;?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These behaviors cannot depend on one particular CPU happening to execute the program in a convenient way.&lt;/p&gt;

&lt;p&gt;Programmers need a defined set of rules they can rely on.&lt;/p&gt;

&lt;p&gt;That leads to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The concurrency semantics provided by the programming language.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Languages Need to Define Concurrency Semantics
&lt;/h2&gt;

&lt;p&gt;A programming language needs to define:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When multiple execution units access state concurrently, which behaviors are allowed and which synchronization and memory-access guarantees programmers may rely on.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Java has the:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java Memory Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go has the:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go Memory Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python is not exactly the same. In this series, the discussion mainly uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python / CPython Concurrency Semantics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These rules need to answer three questions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;What it means for the &lt;code&gt;count&lt;/code&gt; example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Atomicity — which operations are atomic?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;count++&lt;/code&gt; contains &lt;code&gt;read → increment → write&lt;/code&gt;. Without locking, A's and B's steps can interleave and produce only &lt;code&gt;1&lt;/code&gt;. With the same lock, A must completely change &lt;code&gt;count&lt;/code&gt; from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; before B can enter the critical section and continue from &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Visibility — when does a write become visible?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;After A writes &lt;code&gt;count = 1&lt;/code&gt; and releases the lock, B must see &lt;code&gt;1&lt;/code&gt; after acquiring the same lock instead of using the old value &lt;code&gt;0&lt;/code&gt;. With a channel, the Counter Owner must be able to receive the producer's &lt;code&gt;+1&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ordering — which operations are ordered?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;For the same lock, the order is &lt;code&gt;A writes count = 1 → A releases lock → B acquires lock → B reads count = 1&lt;/code&gt;. With a channel, it is &lt;code&gt;producer sends +1 → Counter Owner receives +1 → update count&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In other words, this layer answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What may the programmer rely on?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. Why Do We Still Need to Go Down to the Hardware?
&lt;/h2&gt;

&lt;p&gt;Languages define rules, but those rules cannot implement themselves.&lt;/p&gt;

&lt;p&gt;Java, Go, and Python ultimately rely on several layers to turn language guarantees into behavior on a real machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Concurrency Tools / Synchronization Mechanisms
    └─ Mutex / Atomic / volatile / Channel / Queue
            ↓
Language Memory Model / Concurrency Semantics
    ├─ JMM / Go Memory Model / CPython Concurrency Semantics
    └─ implemented by HotSpot JIT / Go Compiler + Runtime / CPython Runtime
            ↓
Hardware
    ├─ Computer Architecture: Von Neumann Architecture / CPU / Memory
    ├─ Hardware Memory Model: x86-TSO / ARM Memory Model
    ├─ Instruction / CPU Primitive: LOAD / STORE / Atomic RMW / Fence
    └─ Microarchitecture: Cache / Store Buffer / Cache Coherence / Out-of-Order Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modern CPUs use many techniques to improve performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU caches;&lt;/li&gt;
&lt;li&gt;store buffers;&lt;/li&gt;
&lt;li&gt;out-of-order execution;&lt;/li&gt;
&lt;li&gt;atomic instructions;&lt;/li&gt;
&lt;li&gt;different memory-ordering rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Language-level concurrency semantics ultimately have to be implemented on top of these hardware capabilities.&lt;/p&gt;

&lt;p&gt;To understand why language rules and concurrency tools work, we therefore need to understand which behaviors the hardware permits and which constraints it provides.&lt;/p&gt;

&lt;p&gt;Before going deeper into the concrete concurrency semantics of Java, Go, and Python, we need to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What guarantees does the hardware actually provide?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. Next: Start with the Hardware
&lt;/h2&gt;

&lt;p&gt;The next article moves down to the hardware layer.&lt;/p&gt;

&lt;p&gt;It focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;why CPUs need caches;&lt;/li&gt;
&lt;li&gt;how multiple CPU cores coordinate cached data;&lt;/li&gt;
&lt;li&gt;why store buffers and out-of-order execution affect memory-access ordering;&lt;/li&gt;
&lt;li&gt;what atomic instructions provide.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once we understand those pieces, we can return to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java Memory Model
Go Memory Model
Python / CPython Concurrency Semantics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using those rules as the foundation, we can then discuss what guarantees mutexes provide, how runtimes and CPUs implement those guarantees, and how concrete concurrency tools such as atomics and channels fit into the picture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was first published on &lt;a href="https://thinkerqaq.github.io/en/articles/concurrency-series-00/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=article_syndication" rel="noopener noreferrer"&gt;ThinkerQAQ's personal blog&lt;/a&gt; and syndicated here by the author. The original article may be revised over time; please refer to the personal blog for the latest version.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>programming</category>
      <category>softwareengineering</category>
      <category>computerscience</category>
      <category>java</category>
    </item>
  </channel>
</rss>
