单核上的多线程-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
非确定性有限自动机:一次保留多个可能世界
DFA 每次只处在一个状态里。读取一个字符,规则表给出唯一的下一个状态。这个约束让 DFA 很容易实现,也让它的执行轨迹很干净:current_state + character -> next_state。 非确定性有限自动机(Nondeterministic Finite Automaton,NFA)放宽了这个约束。同一个状态读取同一个字符时,可以走到多个下一状态;机器还可以在不消耗字符的情况下移动到别的状态。NFA 的实现方式并不神秘:把“当前状态”从单个值改成一个集合,一次保留所有可能路径。 这篇文章用 Python 写一个 NFA,识别两个字符串:ab 和 ba。这个示例足够小,但能完整覆盖 NFA 的两个核心机制:分支和空转移。 非确定性不是随机 “非确定性”容易被误解成机器随机选择一条路。NFA 的更好理解是:同一时刻保留多条候选路径,只要其中一条路径最后进入接受状态,整个输入就被接受。 问题 DFA NFA 当前状态 一个状态 一组状态 同一输入的下一状态 唯一 可以有多个 是否允许不读字符就移动 不允许 允许 接受条件 当前状态...
2026-05-21
通用性:一种机器怎样模拟另一种机器
前面的文章已经走过两条计算模型路线。图灵机用纸带和指令循环表达计算,lambda 演算用函数应用和表达式规约表达计算。Church 编码进一步说明,数字和布尔值也可以由函数行为构造出来。 这些模型看起来差异很大,却都能表达同一类可计算过程。通用性的核心在于:一种机器可以把另一种机器的程序和数据编码成自己的数据,然后按规则解释这份编码。 本文用一个小型寄存器机解释器展示“程序作为数据”这件事。示例不会追求指令集完整性,只展示通用解释器的基本形状。 从专用机器到通用机器 DFA、PDA、某个固定规则表的图灵机,都像专用机器。规则写死以后,它只能识别或执行某一类任务。 通用机器多了一层编码: 1encoded_program + input_data + interpreter -> output_data 程序不再只是宿主语言里的代码,也可以是一段数据。解释器读取这段数据,根据其中的指令改变配置。只要编码方式足够表达指令和数据,解释器就能模拟很多不同程序。 这个结构正是虚拟机、脚本引擎和 DSL runtime 的共同骨架。 场景 程序数据 解释器 JVM by...
2026-05-20
AgentScope 全景实战:设计速读与生产级十二层装配
把 AgentScope 当成"另一个 LangChain"是上手时最常见的误区。AgentScope v1.x 在 2025 年中做过一次实质上不向后兼容的 API 重构——v0.x 的 actor-based 多机分布式被整体删除,新核心改为 async-ReAct 循环 + MsgHub 显式消息广播。它的设计取向与 LangChain / LangGraph 也不同:假设开发者从一个 ReActAgent 开始,逐层往外加东西——先有一个 agent 能跑,再共享 LLM 单例,再装工具,再装 MCP,再往上做多 agent 路由,最后接观测和部署。这种"渐进式装配"的次序不是教学方便,是它的实际工程姿态——每一层都能独立验证、独立失败、独立替换。 本文分两部分。第一部分是设计速读:把 AgentScope v1.x 的范式翻转、ReAct + MsgHub 核心抽象、与 LangGraph / CrewAI / MAF 的对位、A2A + MCP 协议化分布式、Trinity-RFT 训练集成讲清楚——理解了"为什么&q...
2026-05-21
用 Python 写一台图灵机
上一篇文章已经写出一台最小 DTM:读取当前格,写入一个符号,移动读写头,然后停机。那台机器能展示图灵机的组成,但还不像一段程序。 本文复用同一套 Python 结构,把图灵机写成一个更接近算法的例子:对纸带上的二进制数加一。输入是 1011,输出是 1100。机器需要先移动到数字右侧的空白格,再从右向左处理进位。这个过程会经过多个状态和多次纸带改写,适合观察“有限控制 + 可变纸带”怎样形成完整计算。 二进制加一的规则 二进制加一可以拆成两个阶段。 第一阶段从最左侧开始,一直向右移动,直到读到数字后面的空白格 _。 第二阶段从空白格左移一格,开始处理进位: 当前符号 写入符号 下一步 1 0 继续向左进位 0 1 进位结束,停机 _ 1 数字整体增长一位,停机 以 1011 为例,最右侧两个 1 都会变成 0,左边的 0 变成 1,结果得到 1100。 11011 + 1 = 1100 这里需要两个控制状态: 状态 含义 seek_right 向右寻找数字末尾 carry 从右向左处理进位 halt 计算完成 图灵机的状...
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
写一个 lambda 规约器
上一篇文章把 lambda 演算拆成三种表达式:变量、函数、调用。示例只做了最小的一步替换,用来说明函数应用可以变成表达式重写。 本文把这件事补成一个规约器:给定一个 lambda 表达式,持续执行 beta 规约,打印每一步轨迹,并处理替换过程中最容易出错的变量捕获问题。这个规约器仍然很小,但已经具备解释器和 AST 重写器的基本骨架。 规约器要解决什么 lambda 演算的核心计算规则是 beta 规约: 12(x -> body) argument -> body 中的自由 x 替换成 argument 例如: 12(x -> x) a -> a 更复杂一点的例子: 123(((x -> (y -> x)) a) b) -> ((y -> a) b) -> a 规约器需要完成三件事: 职责 作用 找到可规约位置 判断哪一个调用先执行 做安全替换 只替换自由变量,避免误改绑定变量 重复执行 直到表达式无法继续变化 这和解释器很接近。区别在于解释器通常维护环境和值,lambda 规约器直...
