单核上的多线程-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
图灵机:纸带、读写头和最小通用计算
DFA 只有有限状态。NFA 允许同时保留多个状态。PDA 在有限状态之外加了一只栈,可以处理任意深度的嵌套。图灵机再往前走一步:它把栈换成一条可以读、写、左右移动的纸带。 这个变化很小,却足以把机器能力推到通用计算。图灵机仍然只有有限个控制状态,每一步仍然按规则机械执行;不同的是,机器可以在纸带上写下中间结果,之后再移动回来读取。程序状态和可变存储被明确分开。 本文先写一台最小确定性图灵机(Deterministic Turing Machine,DTM)。示例很小:读写头从第一个字符开始,把当前位置的符号改成 1,向右移动一格,然后停机。下一篇再用同一套结构实现一个稍微有算法味的纸带程序。 图灵机比 PDA 多了什么 PDA 的栈只能操作一端。读写都发生在栈顶,历史只能以后进先出的方式取回。图灵机的纸带更自由:读写头可以向左或向右移动,机器可以反复回到某个位置修改内容。 模型 有限控制 可增长存储 读写位置 典型能力 DFA / NFA 有 无 无 正则语言 PDA 有 栈 栈顶 嵌套结构 图灵机 有 纸带 当前格,可左右移动 通用计算 这个表里...
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
lambda 演算入门:函数为什么足够表达计算
图灵机用纸带、读写头和指令循环描述计算。lambda 演算换了一条路:它不用可变纸带,也不用状态跳转,只保留函数定义、函数调用和变量替换。 这个模型看起来比图灵机更不像机器,却和图灵机有同等表达能力。图灵机强调“有限控制怎样改写存储”,lambda 演算强调“表达式怎样通过函数应用逐步化简”。从工程角度看,它把计算从指令循环翻译成表达式重写。 本文先建立 lambda 演算的最小语法,用 Python 表示变量、函数和调用,再通过一个小例子展示函数怎样返回函数、调用怎样变成替换。 三种表达式 lambda 演算的核心语法只有三种。 形式 示例 含义 变量 x 一个名字 函数 x -> x 接收参数 x,返回表达式 x 调用 (x -> x) a 把函数应用到参数 a 函数也叫抽象,调用也叫应用。为了贴近 Java 程序员熟悉的形式,本文用 x -> x 表示 lambda 表达式,而不用希腊字母写法。 最小模型可以写成一棵表达式树: 1234Expression = Variable(name) | Function(param...
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-09-19
计算机网络 00:一次请求的路径与先修自测
程序执行 sendall(b"network-00"),没有异常,随后打印“发送成功”。此时能否确定服务端已经读取这十个字节?能否确定它已经执行请求,甚至把结果写入磁盘? 这几个问题需要不同的观察点。发送接口返回、对端传输层接收、对端应用读取、业务处理完成,分别对应请求路径上的不同事件。即使它们在一次运行中相隔很短,也不能用前一个事件代替后一个事件的证据。 本篇从一个只监听 127.0.0.1 的回显服务开始。客户端发送固定十字节,服务端读取后原样返回,客户端再逐字节核对。实验只依赖 Python 标准库,不需要真实账号、外部网站或网络管理权限。前置知识是基本程序、整数、字节数组和函数调用;二进制、网络字节序及并发顺序在后文自测。 从监听端口到一条连接 IP 地址定位通信端点所在的网络接口或主机语境,端口进一步区分传输层端点。实验中的 127.0.0.1 是 IPv4 回环地址,连接在本机完成。客户端和服务端可以运行在不同进程里,也可以像附件一样处于同一进程的不同线程。把两者放入一个进程只是方便启动、同步和清理,并没有把 socket 交换替换成函数直接返回...
Announcement
人生只是,守株待兔





