为何多次使用全局变量的表达式会重复读取变量?兼谈SICP并行案例
关于SICP并行执行代码中x=110结果的疑问解答
先看《计算机程序的构造和解释》(SICP)第304页的代码片段:
(define x 10) (parallel-execute (lambda () (set! x (* x x))) (lambda () (set! x (+ x 1))))
为什么第一个进程会两次读取x?
这不是进程“需要”刻意读两次,而是Scheme的表达式求值规则决定的:对于(* x x)这种复合表达式,语言会先分别求值所有操作数,再执行运算符。这里两个x是独立的操作数,所以解释器会分两次读取x的当前值,再把两个值相乘。不是语言要让进程读两次,而是求值逻辑必然会产生两次读取动作。
这种设计的优势是什么?
这种求值策略(应用序求值)是多数编程语言的通用选择,优势在于:
- 语义简洁一致:操作数独立求值的规则简单易懂,编译器、解释器实现成本低,开发者也容易预测基本表达式的行为。
- 适配复杂场景:如果遇到
(* (compute-a x) (compute-b x))这类表达式,两个函数调用本身就是独立的,必须分别执行,复用值反而会违背代码的语义逻辑。 - 灵活控制并发:语言默认不提供表达式级的原子性,把并发同步的控制权交给开发者。如果需要避免这种中间读取被打断的情况,开发者可以显式使用锁或者原子操作,而不是语言强制给所有表达式加不必要的约束。
该问题在C++等其他语言中存在吗?
完全存在,本质是同一类并发竞态问题。比如C++中对应的代码:
#include <thread> int x = 10; int main() { std::thread t1([]{ x = x * x; }); std::thread t2([]{ x = x + 1; }); t1.join(); t2.join(); }
同样可能出现x=110的结果:线程t1先读取一次x=10,线程t2趁机把x改成11,t1再读取一次x=11,相乘后赋值得到110。C++标准也不会默认保证这类表达式的原子性,除非用std::atomic修饰变量,或者显式加锁同步,否则都会存在这类竞态风险。
内容的提问来源于stack exchange,提问作者Enlico
相关产品推荐
相关产品推荐

