单核上的多线程-Python中的 GIL
Created|Updated
|Word Count:191|Reading Time:1mins|Post Views:
GIL (Global Interpreter Lock)的存在虽然无法利用多核,但是可以勉强让系统在在单核上,任何一个线程使用过多时间片/主动放弃 CPU 的时候,让其他线程上下文切入进来。算是尽量跑满CPU吧。Python中的对象很多都是默认线程安全的,GIL的这种不可见的特性,让很多旧的程序依赖起 GIL,以至于无法从Python中移除掉它。GIL 的存在,让 Python 特别适合跑 Nodejs 爬虫一样的 IO 密集型(IO-bound)任务,反而不适合跑CPU 密集型任务(CPU-bound)。但实际上这种混蛋多线程的形式,恐怕还不如 EventLoop 的 Nodejs,因为多了很多 Context Switch 的代价。
Author: magicliang
Copyright Notice: All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.
Related Articles
2026-05-21
通用性:一种机器怎样模拟另一种机器
前面的文章已经走过两条计算模型路线。图灵机用纸带和指令循环表达计算,lambda 演算用函数应用和表达式规约表达计算。Church 编码进一步说明,数字和布尔值也可以由函数行为构造出来。 这些模型看起来差异很大,却都能表达同一类可计算过程。通用性的核心在于:一种机器可以把另一种机器的程序和数据编码成自己的数据,然后按规则解释这份编码。 本文用一个小型寄存器机解释器展示“程序作为数据”这件事。示例不会追求指令集完整性,只展示通用解释器的基本形状。 从专用机器到通用机器 DFA、PDA、某个固定规则表的图灵机,都像专用机器。规则写死以后,它只能识别或执行某一类任务。 通用机器多了一层编码: 1encoded_program + input_data + interpreter -> output_data 程序不再只是宿主语言里的代码,也可以是一段数据。解释器读取这段数据,根据其中的指令改变配置。只要编码方式足够表达指令和数据,解释器就能模拟很多不同程序。 这个结构正是虚拟机、脚本引擎和 DSL runtime 的共同骨架。 场景 程序数据 解释器 JVM by...
2026-05-20
自己写一个解释器:大步语义与递归求值
上一篇已经把字符串解析成 AST。程序结构稳定以后,下一步是给它一套求值规则。最直接的规则叫大步语义(big-step semantics):给定一个表达式和环境,直接得到最终值;给定一个语句和环境,直接得到新的环境。 大步语义适合写成递归解释器。表达式节点递归求值,语句节点递归改变环境。它不会展示每一个中间状态,只关心完整运行后的结果。 本文会实现一个小语言。它支持数字、布尔值、变量、加法、乘法、小于、赋值、顺序执行、条件分支和 while 循环。 大步语义关心最终关系 大步语义可以先记成两条关系: 12expression + environment => valuestatement + environment => new_environment 表达式会产生值,语句会改变环境。环境是变量名到值的映射。 例如: 1x + 1, {x = 2} => 3 赋值语句会得到一个新环境: 1x = x + 1, {x = 2} => {x = 3} 这套写法和 Java 程序员熟悉的递归下降解...
2026-05-21
确定性有限自动机:状态、输入和接受条件
上一篇用指称语义把程序翻译成 Python 函数。程序语义这一段到这里已经形成闭环:AST 给出结构,大步语义、小步语义和指称语义分别给出不同角度的含义。接下来进入自动机。问题从“程序怎样执行”换成“机器怎样根据输入移动到下一个状态”。 确定性有限自动机(Deterministic Finite Automaton,DFA)是自动机部分的起点。它没有变量表,没有栈,没有堆,也没有可写纸带;它只记住一个当前状态,然后逐个读取输入字符。每读一个字符,机器就根据一张规则表换到下一个状态。输入读完后,当前状态如果属于接受状态,字符串就被接受;否则被拒绝。 这个模型很小,却足够解释很多工程直觉:订单状态流转、协议解析、词法分析、简单规则匹配,都可以先压成“当前状态 + 输入 -> 下一个状态”的形式。 从业务状态机收缩到形式模型 Java 程序员对状态机并不陌生。订单可以从 CREATED 变成 PAID,再变成 SHIPPED;审批单可以从 DRAFT 变成 SUBMITTED,再变成 APPROVED 或 REJECTED。这些状态机通常带着业务动作、数据库事务、权限检查、通知...
2026-05-20
重读《计算的本质》:给 Java 程序员的可计算性入门
《计算的本质》容易读散。它表面上在讲 Ruby、抽象语法树、自动机、lambda 演算、停机问题、类型系统,读起来像一串互不相干的经典概念;主线可以压成一句话: 计算就是用有限的规则,描述一个可以机械执行的状态变化过程。 这句话一旦立住,全书就不再是术语集合。第 2 章讲程序语义,是在回答“规则写成程序以后,它到底是什么意思”。第 3、4、5 章讲自动机,是在回答“状态机器多一点存储以后,能力会怎样增长”。第 6、7 章讲 lambda 演算和通用系统,是在回答“形式极其简单的规则,为什么也能表达通用计算”。第 8、9 章讲不可判定性和抽象解释,是在回答“即使计算模型已经足够强,为什么仍然有些问题不能精确解决”。 这套重读系列不打算复述原书。旧文《计算的本质》已经做过概念地图,这个系列换一种路线:用 Python 重写原书的核心实验,并在每一篇里把概念翻译成 Java 程序员熟悉的工程直觉。 为什么原书会显得难 这本书难在视角切换。数学和 Ruby 都会带来阻力,但它们不是最大障碍。 多数业务开发者习惯从库、框架、API、对象协作去理解程序。一个方法能不能跑,通常看参数、返...
2026-05-21
停机问题:为什么某些判断没有通用程序
上一篇文章用寄存器机解释器说明了通用性:程序可以编码成数据,解释器可以读取这份数据并模拟执行。通用性带来强大表达能力,也带来一个边界问题:能不能写一个程序,判断任意程序在任意输入上是否会停机。 答案是否定的。停机问题说明,不存在一个对所有程序和输入都正确的通用停机判定器。工程里可以做超时、步数限制、静态分析和资源预算,但这些方法要么不完整,要么会保守拒绝,要么只覆盖有限范围。 本文先用一个可运行的有界检查器展示工程近似,再用自引用反证解释为什么通用判定器不存在。 问题形式 停机问题可以写成一个理想函数: 1halts(program, input_data) -> True / False 它的承诺是: 返回值 含义 True program(input_data) 最终会停机 False program(input_data) 会一直运行 难点在“任意程序”和“任意输入”。判断某个具体程序很容易;判断所有程序则会遇到自引用。 工程里常见的近似:有界执行 最朴素的方法是只运行有限步。如果步数内停机,就返回 True;如果步数用完还没停,就返回 Fa...
2026-05-20
程序先变成语法树:从字符串到 AST
前一篇用 dataclass 手工创建了一个表达式: 1program = Add(Number(1), Add(Number(2), Number(3))) 这已经能演示解释器的基本形状,但它还绕开了一个关键步骤。真实程序通常从文本开始。读者写下的是 x + (2 + y) + 3,解释器需要先把这段文本变成结构化对象,后面的求值规则才有东西可操作。 本文做这件事:写一个很小的 tokenizer 和 parser,把字符串转成 AST(Abstract Syntax Tree,抽象语法树),再对 AST 求值。 程序文本进入系统后的三步 一段源码进入解释器后,可以先粗略拆成三步: 1source text -> tokens -> AST -> value source text 是原始字符串。tokens 是词法单元列表,比如数字、变量名、加号、括号。AST 是语法结构,表达“谁和谁相加”“括号包住哪一段”。value 是执行结果。 本文只实现一个小语言。它支持整数、变量、加法和括号: 1231 + 2x + 3x + (2 + y) + 3 语法规则也...



