1234机器人 从 Java、Go、Python 三种语言的锁规则出发,逐一检查它们对 Atomicity、 Visibility 和 Ordering 的保证力度

从 Java、Go、Python 三种语言的锁规则出发,逐一检查它们对 Atomicity、

Visibility 和 Ordering 的保证力度。下面是对照原文的完整梳理。

一、定位:这篇文章在讲什么


ThinkerQAQ 的并发编程系列有一条清晰的递进线:


| 篇目 | 主题 | 分析层 |

|:---:|------|:------:|

| (零) | 并发问题的范围与讨论边界 | 问题定义 |

| (一) | 从 count++ 到硬件层的原子性、可见性、有序性 | 硬件层(CPU Cache、Store Buffer、Out-of-Order) |

| (二) | 语言内存模型——程序员可以依赖的规则 | 内存模型层(JMM、Go Memory Model) |

| (三) | 互斥锁——语言层的原子性、可见性与有序性 | 语言锁规则层 |

| (四) | 互斥锁是怎么实现的(预告) | 实现层 |


这篇文章不从 "Mutex 怎么用" 出发,而是从 "锁的规范说了什么" 出发,画出了 Java、Go、Python 三条路各自对并发三要素的保证边界。


---

二、Java:synchronized 与 Monitor


Java 的 synchronized 锁定对象关联的 Monitor,其规则在 JLS(Java Language Specification)中有明确定义。

Atomicity

"Only one thread at a time may hold a lock on a monitor." — JLS §17.1


同一时刻只有一个线程能持有 Monitor,因此只有它能进入临界区。其他使用同一 Monitor 的线程只能等待。这是互斥锁最基础的保证。

Visibility + Ordering

"An unlock on a monitor happens-before every subsequent lock on that monitor." — JLS §17.4.5


这一条规则同时保证了 Visibility 和 Ordering:

对于 Visibility:A 的写入发生在 unlock 之前,B 的读取发生在后续 lock 之后。通过 happens-before 的传递性,

A 在临界区中的写入对 B 可见。

对于 Ordering:A 内部的写入顺序、unlock → lock 和 B 内部的读取顺序被连成一条 happens-before 链。因此,

B 不能看到 ready = true, counter = 0 这种违反程序顺序的结果。


Java 的 synchronized 是三者中最严格的——一条 happens-before 规则同时覆盖了可见性和有序性。


---

三、Go:sync.Mutex


Go 的 sync.Mutex 不绑定某个 Goroutine,也不提供可重入语义。Go 对 Mutex 的语义规范定义在 Go Memory Model 中。

"If the lock is already in use, the calling goroutine blocks until the mutex is available."


一个 Goroutine 持有 Mutex 时,其他 Goroutine 会等待,不能进入受同一 Mutex 保护的临界区。

Visibility + Ordering

"For any sync.Mutex or sync.RWMutex variable l and n < m, call n of l.Unlock() is synchronized before call m of l.Lock() returns."


这条规则与 Java 的 happens-before 类似:

Visibility:A 的写入 sequenced-before Unlock,Unlock 又 synchronized-before B 的 Lock 返回,B 的读取发生在 

Lock 返回之后。这些关系组成 happens-before → A 的写入对 B 可见。

Ordering:同一条 happens-before 链把 A 内部的写入顺序、Unlock → Lock 和 B 内部的读取顺序连起来。


Go 的内存模型体系使用 sequenced-before → synchronized-before → happens-before 三级关系来组织,比 Java 多

一层分解,但最终效果等价。


---

四、Python:threading.Lock


Python 是三者中最特殊的一个——它的 Lock 文档中没有明确定义 release → acquire 的 happens-before 关系。

Atomicity

"A primitive lock is a synchronization primitive that is not owned by a particular thread when locked."

"All methods are executed atomically."


Lock 已经被获得时,其他线程的 acquire() 会等待 → 同一时刻只有一个线程能进入受同一把 Lock 保护的临界区。Atomicity 是明确保证的。

Visibility 和 Ordering —— 没有正式保证


ThinkerQAQ 在这里精准指出一个区别:Python 没有像 Java、Go 那样从一条正式的 Memory Model 规则推出 Visibility 和 Ordering。

"Visibility 和 Ordering 则不能像 Java、Go 那样从一条正式的 Memory Model 规则推出。Python 没有定义 

release → acquire 的 happens-before 关系;官方文档只把 Lock 定义为 synchronization primitive。"


因此,在 CPython 中,Lock 如何落实跨临界区的可见性和顺序边界,需要进一步下钻到实现层去看(这正是文章预告的下一篇的内容)。


文章特别指出:

"Python 程序应该使用同一把 Lock 同步共享状态,而不是依赖 GIL 或某条字节码当前是否可中断。"


这是很多 Python 并发入门的常见误区——GIL 只保护单个字节码操作,不能替代互斥锁的语义保证。


—--

五、三者的对比


| 维度 | Java synchronized | Go sync.Mutex | Python threading.Lock |

|:----:|:------------------:|:---------------:|:----------------------:|

| Atomicity | 有规则 | 有规则 | 有规则 |

| Visibility | unlock → lock happens-before 链保证 | Unlock synchronized-before Lock 保证 | 无正式 Memory Model 规则 |

| Ordering | 同在 happens-before 链中保证 | 同在 happens-before 链中保证 | 无正式保证 |

| 规则出处 | JLS §17.4.5 | Go Memory Model - Locks | 文档仅标注 synchronization primitive |

| 可重入 | 是(Monitor) | 否 | 否 |

| 需要下钻实现层? | 否(语言层规则已够) | 否 | 是,靠 CPython 的 GIL + 底层调度 |


---

六、结构上的巧思


ThinkerQAQ 这一篇在整体系列中有一个非常清晰的定位:它从"上一篇(语言内存模型)"出发,切到"锁的规范"这一

中间层——既不是纯理论的内存模型推导,也不是纯底层的 CPU 指令实现。[citation:34.41]

这一篇停在语言层:从 Atomicity、Visibility 和 Ordering 三个方面,看一把 Mutex 向程序员提供什么保证。


下一篇预告——"互斥锁是怎么实现的"——会下钻到 Runtime 和 CPU 层,用 atomiccompareexchange、futex、内

存屏障来解释这些保证凭什么能落地。这个横跨 硬件 → 内存模型 → 锁规范 → 实现 的四层递进结构,是 ThinkerQAQ 

这个系列最有价值的地方。


发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Top