单核上的多线程-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-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
不用数字也能计算:Church 编码
lambda 演算的基本语法只有变量、函数和调用。前一篇已经写出规约器,说明函数应用可以通过替换一步步化简。 接下来的问题是数据从哪里来。日常编程语言有数字、布尔值、条件表达式和集合;纯 lambda 演算没有这些原语。Church 编码给出一种极端答案:数据可以表示成行为。布尔值是“选择左边还是右边”的函数,数字是“把某个函数重复执行多少次”的函数。 本文用 Python 高阶函数实现 Church boolean 和 Church numeral,再把它们映射回工程直觉。 数据也可以是行为 普通编程里,数字 2 看起来是一块数据。Church 编码换一个定义: 12 = 接收函数 f 和初始值 x,然后执行 f(f(x)) 也就是说,数字不再保存为整数对象,而是保存为“重复应用函数的次数”。 布尔值也类似: 12true = 选择第一个分支false = 选择第二个分支 这个视角把“值是什么”改成“值能做什么”。在 lambda 演算里,只要函数和调用足够表达这些行为,就可以构造出数字、布尔值和条件选择。 Church 布尔值 Church boolean 的定义很短。 ...
2026-05-21
图灵机:纸带、读写头和最小通用计算
DFA 只有有限状态。NFA 允许同时保留多个状态。PDA 在有限状态之外加了一只栈,可以处理任意深度的嵌套。图灵机再往前走一步:它把栈换成一条可以读、写、左右移动的纸带。 这个变化很小,却足以把机器能力推到通用计算。图灵机仍然只有有限个控制状态,每一步仍然按规则机械执行;不同的是,机器可以在纸带上写下中间结果,之后再移动回来读取。程序状态和可变存储被明确分开。 本文先写一台最小确定性图灵机(Deterministic Turing Machine,DTM)。示例很小:读写头从第一个字符开始,把当前位置的符号改成 1,向右移动一格,然后停机。下一篇再用同一套结构实现一个稍微有算法味的纸带程序。 图灵机比 PDA 多了什么 PDA 的栈只能操作一端。读写都发生在栈顶,历史只能以后进先出的方式取回。图灵机的纸带更自由:读写头可以向左或向右移动,机器可以反复回到某个位置修改内容。 模型 有限控制 可增长存储 读写位置 典型能力 DFA / NFA 有 无 无 正则语言 PDA 有 栈 栈顶 嵌套结构 图灵机 有 纸带 当前格,可左右移动 通用计算 这个表里...
2026-05-20
指称语义:把程序翻译成 Python 函数
前两篇用操作语义解释程序。大步语义直接给最终结果,小步语义展示每一步规约。第 2 章还有第三种视角:指称语义(denotational semantics)。 指称语义把程序映射到某个更基础的对象。在本文的小语言里,表达式会变成一个 Python 函数,语句也会变成一个 Python 函数。程序的含义从“机器怎样跑”转到“它等价于哪个函数”。 对 Java 程序员来说,可以先把它理解成: 12Function<Environment, Value> // 表达式的含义Function<Environment, Environment> // 语句的含义 本文继续沿用前面的 AST,用 Python 函数重写这套小语言的含义。 三种语义回答三类问题 同一段程序可以从三种角度解释。 语义 关心的问题 本系列里的实现 大步语义 运行完成后得到什么 递归解释器 小步语义 每一步怎样变化 规约机器 指称语义 程序等价于什么对象 函数翻译器 大步和小步都属于操作语义。它们会描述程序在某种抽象机器上怎样执行。指称语义换成另一...
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"...
Announcement
人生只是,守株待兔

