面试准备

最近改动

更新题目 线程同步方式有哪些

题目 bagu-os-24 · 修改(rev 5) · cc · 2026-10-26 00:29

body

@@ -17,5 +17,5 @@
 ### 同步要解决什么
 
-同一个进程里的线程共享内存。多个线程同时读写同一个变量、其中至少一个在写,最后的结果就取决于它们的指令恰好怎么交错,这叫竞争条件(race condition)。最常见的例子是 `count += 1`:它其实是「读出来、加一、写回去」三步,线程 A 读完还没写回时,线程 B 也读到了同一个旧值,两个线程各写回一次,结果只加了一。[[p:me/python-concurrency]] 里有实测:四个线程各加 200000 次,在「读」和「写」之间插一句 `time.sleep(0)` 让出 CPU、把这个窗口撑大,不加锁只得到 200202;去掉 `sleep` 在 CPython 3.12 上反而恰好是 800000,那只是因为 CPython 3.12 只在少数几个固定位置检查要不要切换线程,这段循环的读和写之间恰好没有这样的位置,不说明不加锁是安全的。
+同一个进程里的线程共享内存。多个线程同时读写同一个变量、其中至少一个在写,最后的结果就取决于它们的指令恰好怎么交错,这叫竞争条件(race condition)。最常见的例子是 `count += 1`:它其实是「读出来、加一、写回去」三步,线程 A 读完还没写回时,线程 B 也读到了同一个旧值,两个线程各写回一次,结果只加了一。[[p:kb/python-concurrency]] 里有实测:四个线程各加 200000 次,在「读」和「写」之间插一句 `time.sleep(0)` 让出 CPU、把这个窗口撑大,不加锁只得到 200202;去掉 `sleep` 在 CPython 3.12 上反而恰好是 800000,那只是因为 CPython 3.12 只在少数几个固定位置检查要不要切换线程,这段循环的读和写之间恰好没有这样的位置,不说明不加锁是安全的。
 
 所以同步要解决两类问题:
@@ -55,5 +55,5 @@
 - 「叫醒一个等待者」和「叫醒全部」两个动作,C++ 叫 `notify_one` / `notify_all`,Python 叫 `notify()` / `notify_all()`(Python 的 `notify()` 就是 `notify_one`),Java 的 `Condition` 上叫 `signal` / `signalAll`,`Object` 上叫 `notify` / `notifyAll`。下文讲道理时用 C++ 的名字,代码里是 Python 的名字。
 
-等的一方拿锁后用 `while` 检查条件,不成立就 `wait`;改条件的一方拿锁、改条件、再通知。Python 的写法见 [[p:me/python-concurrency]],完整的有界队列见 [[q:bagu-conc-15]] 和 [[q:bagu-conc-17]]。
+等的一方拿锁后用 `while` 检查条件,不成立就 `wait`;改条件的一方拿锁、改条件、再通知。Python 的写法见 [[p:kb/python-concurrency]],完整的有界队列见 [[q:bagu-conc-15]] 和 [[q:bagu-conc-17]]。
 
 **为什么必须是 `while` 而不是 `if`。** 醒来不代表条件成立,原因有两个。第一,从被叫醒到重新拿到锁之间有一段空档,别的线程可能趁这时把条件又改掉了。第二,虚假唤醒(spurious wakeup):POSIX、Java、C++ 的规范都允许 `wait` 在没人通知的情况下返回,原因见追问「什么是虚假唤醒」。第一种情况可以稳定复现:队列里只放一个元素,却用 `notify_all` 叫醒两个消费者。