单核上的多线程-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
自己写一个解释器:大步语义与递归求值
上一篇已经把字符串解析成 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-10-07
Python 23:如何替换时间而不替换真实行为
同一段 "One two one" 在两组可控时间读数下走出不同的可检查结果:开始前读到 5、deadline 为 5,任务仍为 queued;开始读到 1、计算后读到 6,任务记为 failed,result 保持 null,原因里保留 TimeoutError。没有在测试里 sleep 等待时间到来,也没有把 SQLite 换成一份声称“差不多”的字典。 第22 篇的测试分层强调的是断言真实状态,而非统计 mock 被调用的次数。本篇只替换时间读数,用真实任务函数和真实临时数据库检验超时后的结果;还保留 16 的实际文件句柄关闭路径。 让时钟可控,工作仍真实 app.run 增加可选的 clock 和绝对时间 deadline 参数,默认时钟是 time.monotonic。当前顺序执行在开始前与计算后各检查一次:若开始前已到 deadline,抛 TimeoutError 且保留排队状态;若计算后才超时,由原来捕获执行故障的路径设置 failed、记录错误并抛出带原因的 TaskExecutionError。SQLite 仓储把同一时钟与 deadli...

2026-10-07
Python 10:惰性管道在哪里积累成本
词元改成生成器后,还要过滤停用词并分批消费。把数据分成两块,第一块里正好有两个词;取出第一批时如果第二块也被读取,管道就有了不必要的提前工作。只看最终词频相同,无法发现上游是否被额外消费。 09 用读取块记录验证过第一次 next 的范围;本章继续在 tasklab 上组合过滤与批处理,同时保持未过滤任务的词频业务合同。 先知道过滤针对谁 word_batches(chunks, skip=(), size=64) 将 iter_words(chunks) 产生的词元交给生成器表达式筛选,再用 Python 3.12 标准库 itertools.batched 组成固定容量的元组批次。核心路径是: 123skipped = frozenset(skip)words = (word for word in iter_words(chunks) if word not in skipped)return batched(words, size) 过滤条件作用于单个词元,不是整份任务结果;skip={"skip"} 时得到 "one&...
2026-05-20
指称语义:把程序翻译成 Python 函数
前两篇用操作语义解释程序。大步语义直接给最终结果,小步语义展示每一步规约。第 2 章还有第三种视角:指称语义(denotational semantics)。 指称语义把程序映射到某个更基础的对象。在本文的小语言里,表达式会变成一个 Python 函数,语句也会变成一个 Python 函数。程序的含义从“机器怎样跑”转到“它等价于哪个函数”。 对 Java 程序员来说,可以先把它理解成: 12Function<Environment, Value> // 表达式的含义Function<Environment, Environment> // 语句的含义 本文继续沿用前面的 AST,用 Python 函数重写这套小语言的含义。 三种语义回答三类问题 同一段程序可以从三种角度解释。 语义 关心的问题 本系列里的实现 大步语义 运行完成后得到什么 递归解释器 小步语义 每一步怎样变化 规约机器 指称语义 程序等价于什么对象 函数翻译器 大步和小步都属于操作语义。它们会描述程序在某种抽象机器上怎样执行。指称语义换成另一...

2026-10-07
Python 18:类型提示能发现什么错误
在源码里写 service.submit("word_count", 42),Pyright 1.1.404 指出第二个参数是整数,预期是字符串;把相同数字藏在 json.loads 的返回值中,静态检查却没有报错,真正执行时由原来的 submit 抛出 TypeError: text must be a string。不能把“类型检查通过”写成“输入已经安全”。两次判断发生在不同的边界。 第17 篇的 CLI对解码和输入长度的检查仍保留。本篇加一层类型明确的任务接口,并不重写任务引擎或重新实现校验。示例仍使用 tasklab 的进程内 store,提交、执行、查询、取消遵守原状态契约。 接口是可检查的承诺,不是运行时门卫 examples/python-language/src/tasklab/typed.py 定义 TaskGateway(Protocol),列出四个操作及其参数和返回值;MemoryTaskGateway 把这些操作委托给已经存在的 app。变量标为 TaskGateway 时,检查器只需看方法的形状,不必让任务对象继承这个类。这是 P...

2026-10-07
Python 25:pyproject 如何生成可安装制品
在24 的隔离环境里,PYTHONPATH=src 能运行任务,但这并不测试安装。现在从项目 pyproject.toml 构建 wheel 与 sdist,再把 wheel 装进另一个没有 pip、也不把源码目录加入 PYTHONPATH 的 venv;从仓库外运行生成的 tasklab 命令,先提交,再通过另一个进程执行和查询同一 SQLite 文件,最后验证重复执行被拒绝。安装后 tasklab.__file__ 位于新环境的 site-packages,不是工作树的 src。 这次新增的是分发边界,不是新任务引擎。21 篇的数据库合同和取消逻辑仍由原模块执行。发布到 PyPI、跨平台兼容性和非信任网络输入不在本章验收范围。 分清三个文件和三种角色 pyproject.toml 的 [build-system] 选择 setuptools.build_meta,将后端 setuptools==80.9.0、构建 wheel 所需 wheel==0.45.1 固定;[project] 保留 name=tasklab、version=0.1.0、requires-python...
Announcement
人生只是,守株待兔





