单核上的多线程-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-20
刚好够用的 Python:为《计算的本质》准备实验语言
上一篇把《计算的本质》的主线压成了一句话:计算就是用有限规则描述可以机械执行的状态变化过程。从这一篇开始,系列进入代码实验。后面的解释器、自动机、图灵机、lambda 演算、抽象解释都需要一套很小的实验语言。 本文选择 Python。它在这里承担两个角色:一方面,代码足够短,方便把注意力留给计算模型;另一方面,Python 和 Java 的距离刚好合适,读者可以看清哪些部分属于模型本身,哪些部分只是语言样板。 本文按 Python 3.12 写代码。大部分示例在 Python 3.10 及以上也能运行,因为结构化模式匹配从 Python 3.10 开始可用。 只需要一只小工具箱 后续文章不会用到完整 Python 知识。实验语言只需要七件工具。 工具 用途 后续出现位置 Java 对照 dataclass 定义不可变语法节点 AST、图灵机配置、lambda 项 record match 按节点类型分派 解释器、规约器 switch + 模式匹配 函数对象 把规则当值传递 指称语义、lambda 演算 Function<T, R> 闭包 保...
2026-05-18
模块化与动态加载的跨平台对照:从 ClassLoader 到 BEAM、ALC、dlmopen
写在前面 这篇是 《从 OSGi 到 Jigsaw:Java 模块化、SPI 与类加载器切换的真相》 的续篇。前文只在 Java 一个语种内部比较 OSGi、JBoss Modules、JPMS 三套方案。这篇把视野拉宽——其他主流平台对"模块化、动态加载、热替换"这套问题各自给出了怎样的回答,谁的方案到工业级别还活着,谁干脆放弃了。 讨论的对象是六家:.NET(CoreCLR)、Erlang/BEAM、Node.js(ESM)、Python(CPython)、C/C++(dlopen 系)、Go。每一家都拿同一组问题去问:模块身份是什么、谁来隔离、能不能加新版、能不能卸老版、老对象怎么办、SPI 怎么做。 一、坐标系:把"模块化"切成五件事 跨平台比较最容易陷入"功能罗列"。先把坐标系定清楚,对照表才有意义。模块化这个词在工程上至少要拆成五件事: 隔离单元:什么是平台眼里的"一个模块"?文件、命名空间、加载器、还是进程? 命名空间:同名符号能不能在系统里同时存在两份? 可见性:模块内部的东西,能...
2026-05-21
抽象解释:用保守近似理解程序
停机问题说明,对任意程序做完备且正确的停机判断是不可能的。工程分析工具并没有因此失去意义。它们换了目标:不追求精确理解所有程序,而是用可计算的抽象信息给出有用结论。 抽象解释就是这个方向的代表。它把具体值集合映射到抽象域里,用保守规则模拟程序执行。只要抽象规则覆盖了所有可能的具体行为,分析结果就可以用于告警、优化或拒绝高风险程序。 本文用区间分析做一个最小模型:变量不再保存单个整数,而是保存可能取值范围。 从具体值到抽象值 具体执行关心单个值: 123x = 3y = 5z = x + y = 8 抽象执行关心值的集合。若只知道 x 在 0 到 10 之间,y 等于 5,就可以推出: 123x in [0, 10]y in [5, 5]z = x + y => z in [5, 15] 这个结果不精确。z 不一定等于区间里的每个数,但所有可能结果都被包含在 [5, 15] 里。保守性比精确性更重要:不能漏掉可能发生的值。 区间抽象域 区间可以用两个端点表示。 12345678910111213141516from dataclasses import dataclass@...
2026-05-20
重读《计算的本质》:给 Java 程序员的可计算性入门
《计算的本质》容易读散。它表面上在讲 Ruby、抽象语法树、自动机、lambda 演算、停机问题、类型系统,读起来像一串互不相干的经典概念;主线可以压成一句话: 计算就是用有限的规则,描述一个可以机械执行的状态变化过程。 这句话一旦立住,全书就不再是术语集合。第 2 章讲程序语义,是在回答“规则写成程序以后,它到底是什么意思”。第 3、4、5 章讲自动机,是在回答“状态机器多一点存储以后,能力会怎样增长”。第 6、7 章讲 lambda 演算和通用系统,是在回答“形式极其简单的规则,为什么也能表达通用计算”。第 8、9 章讲不可判定性和抽象解释,是在回答“即使计算模型已经足够强,为什么仍然有些问题不能精确解决”。 这套重读系列不打算复述原书。旧文《计算的本质》已经做过概念地图,这个系列换一种路线:用 Python 重写原书的核心实验,并在每一篇里把概念翻译成 Java 程序员熟悉的工程直觉。 为什么原书会显得难 这本书难在视角切换。数学和 Ruby 都会带来阻力,但它们不是最大障碍。 多数业务开发者习惯从库、框架、API、对象协作去理解程序。一个方法能不能跑,通常看参数、返...
2026-05-21
正则表达式如何变成自动机
DFA 和 NFA 已经把“字符串识别”拆成了状态、输入字符、转移规则和接受状态。正则表达式站在更高一层:开发者写 a(b|c)*,机器负责把它变成可以执行的匹配过程。 这篇文章只处理传统正则表达式的核心结构:字面量、连接、选择和重复。现代正则 API 还包含捕获组、环视、反向引用、贪婪/非贪婪策略等扩展;这些扩展属于工程实现层,不影响本文要展示的主线。 核心链路很短: 1regex text -> regex AST -> NFA design -> accepts(text) 本文不写正则 parser,直接手工构造 AST。前面文章已经展示过“字符串到 AST”的方法,这里把注意力放在第二步:一个正则 AST 节点怎样编译成 NFA。 正则表达式先变成结构 a(b|c)* 不是一串神秘字符。按传统正则语义,它可以拆成四种结构。 正则片段 AST 节点 含义 a Literal("a") 匹配一个字符 bc Concatenate(Literal("b"), Literal("c"...
2026-05-21
非确定性有限自动机:一次保留多个可能世界
DFA 每次只处在一个状态里。读取一个字符,规则表给出唯一的下一个状态。这个约束让 DFA 很容易实现,也让它的执行轨迹很干净:current_state + character -> next_state。 非确定性有限自动机(Nondeterministic Finite Automaton,NFA)放宽了这个约束。同一个状态读取同一个字符时,可以走到多个下一状态;机器还可以在不消耗字符的情况下移动到别的状态。NFA 的实现方式并不神秘:把“当前状态”从单个值改成一个集合,一次保留所有可能路径。 这篇文章用 Python 写一个 NFA,识别两个字符串:ab 和 ba。这个示例足够小,但能完整覆盖 NFA 的两个核心机制:分支和空转移。 非确定性不是随机 “非确定性”容易被误解成机器随机选择一条路。NFA 的更好理解是:同一时刻保留多条候选路径,只要其中一条路径最后进入接受状态,整个输入就被接受。 问题 DFA NFA 当前状态 一个状态 一组状态 同一输入的下一状态 唯一 可以有多个 是否允许不读字符就移动 不允许 允许 接受条件 当前状态...



