码农高天
即使协程在IO场景更轻量,多线程仍依赖操作系统抢占式调度防止长计算任务独占CPU,是不可替代的保底方案。
AI-assisted summary of 【python】听说Python的多线程是假的?它真的没有存在的价值么? — original source. Check the original for full context.
Source published:
Key ideas
- 针对“Python 多线程毫无意义”的论调,多线程概念早在 20 世纪 50 至 60 年代就已出现,远早于 2001 年 IBM Power4 及 2005 年 PC 端多核 CPU 的面世,其初衷并非仅为了利用多核硬件。 (0:00)
- 在维基百科爬虫抓取实验中,单线程顺序执行耗时约 1 秒多,而改用双线程并发处理后耗时降至约 0.6 秒,证明多线程在 IO 瓶颈任务中仍能带来显著的性能翻倍效果。 (1:22)
- 协程虽然轻量且避免了竞态条件,但其协作式调度高度依赖 `await` 显式放权;一旦遭遇耗时长的计算任务或第三方黑盒函数,整个事件循环将彻底陷入卡死状态。 (2:51)
- 多线程凭借操作系统的抢占式调度(时间片轮转)机制,能有效防止单一任务独占 CPU 并保障低延迟任务的响应,即便是 `asyncio` 的 `run_in_executor` 底层依然依赖线程池运行。 (3:52)
Detailed notes
## Background 该视频由技术博主“码农高天”于 2024 年 5 月 25 日发布在 YouTube 平台上。视频旨在探讨网络上广为流传的“Python 多线程是假的、毫无存在价值”这一说法,并深入分析这种观点的偏颇之处与多线程的真实价值。
## TL;DR 高天从计算机硬件发展史切入,指出多线程概念远早于多核 CPU 面世,其核心使命是在不显式切换任务的前提下盘活 CPU 算力,通过维基百科爬虫实验证明多线程在 IO 瓶颈任务下依然能实现接近翻倍的加速。随后他对比了多线程与现代协程(`asyncio`)的区别,指出协程高度依赖 `await` 显式协作放权,一旦遭遇长耗时计算或不可控的第三方阻塞函数就会卡死事件循环。相比之下,多线程依托操作系统的抢占式调度,能强制按时间片轮转分配 CPU,有效防止低延迟交互任务被饿死,在现代异步体系底层依然扮演着不可替代的角色。
## Atmosphere & style 视频整体呈现出清醒、务实且逻辑严密的硬核科普风格。高天的表达直率接地气,开篇便直击网络上人云亦云“只知 GIL 却不知底层”的技术盲区,言语间既有对教条主义的犀利驳斥,又有对技术本质的耐心拆解。配合清晰的代码跑分与架构对比,整场讲解节奏紧凑、循序渐进,带给观众一种拨开迷雾、打破技术成见的通透感。
## Bulletin - [00:00](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=0s) 针对“Python 多线程毫无意义”的论调,多线程概念早在 20 世纪 50 至 60 年代就已出现,远早于 2001 年 IBM Power4 及 2005 年 PC 端多核 CPU 的面世,其初衷并非仅为了利用多核硬件。 - [01:22](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=82s) 在维基百科爬虫抓取实验中,单线程顺序执行耗时约 1 秒多,而改用双线程并发处理后耗时降至约 0.6 秒,证明多线程在 IO 瓶颈任务中仍能带来显著的性能翻倍效果。 - [02:51](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=171s) 协程虽然轻量且避免了竞态条件,但其协作式调度高度依赖 `await` 显式放权;一旦遭遇耗时长的计算任务或第三方黑盒函数,整个事件循环将彻底陷入卡死状态。 - [03:52](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=232s) 多线程凭借操作系统的抢占式调度(时间片轮转)机制,能有效防止单一任务独占 CPU 并保障低延迟任务的响应,即便是 `asyncio` 的 `run_in_executor` 底层依然依赖线程池运行。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=0s) 许多人盲目批评 Python 多线程为“鸡肋”或“假多线程”,往往仅停留在 GIL 的表面认知,却不清楚其底层锁定的具体机制和使用场景。 - [00:35](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=35s) 多线程的核心价值在于无需显式任务切换即可让 CPU 同时处理多项任务,而非仅仅用于压榨多核处理器的峰值算力。 - [00:48](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=48s) 计算机发展史上,首款商用多核 CPU(IBM Power4)直到 2001 年才发布,个人电脑更推迟至 2005 年,而多线程早在此前数十年便已被广泛应用。 - [01:02](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=62s) 在单线程面对文件读写、网络数据传输等 IO 瓶颈任务时,CPU 会处于闲置摸鱼状态,多线程可充分利用等待窗口期执行运算。 - [01:22](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=82s) 通过双线程抓取维基百科页面的实测,耗时从单线程的 1 秒出头缩短至约 0.6 秒,直观证实了 Python 多线程在 IO 场景下的加速价值。 - [02:51](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=171s) 协程(asyncio)因轻量且运行在单线程内、无数据竞态冲突而受到青睐,但这并未完全取代多线程的存在必要性。 - [03:15](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=195s) 协程属于协作式调度模型,必须由开发者通过 `await` 主动放弃控制权,无法强制中断正在执行的同步逻辑。 - [03:24](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=204s) 若系统中同时存在耗时较长的计算任务与低延迟响应任务,纯协程方案会导致事件循环卡死,令小任务遭遇严重饥饿或超时。 - [03:38](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=218s) 在计算密集循环中强行人工插入放权点不仅破坏代码可读性,而且面对第三方闭源依赖包时开发者根本无法修改源码注入放权逻辑。 - [03:52](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=232s) 操作系统为多线程提供抢占式调度,能通过系统时钟强制分配时间片,确保即使某一线程陷入繁重计算,其他响应线程仍能正常分得 CPU 资源。 - [04:44](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=284s) 现代异步编程中通过 `run_in_executor` 隔离长耗时任务的常见做法,其底层依赖的依然是多线程池或多进程池机制。 - [05:05](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=305s) 开发者应当清晰掌握技术边界与工具特性,因地制宜选择多线程或协程,而非盲目听信网络上的断章取义。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=0s) 多线程的历史渊源与IO瓶颈任务的加速价值
如果你是 Python 的使用者,经常会在网络或社群中听到诸如“Python 的多线程是假的”、“Python 的多线程是鸡肋”、“Python 多线程毫无意义”这类的论调。码农高天直言不讳地指出:“说这些话的人呢,其中有挺大一部分,也无非就是知道一个GIL,甚至GIL到底lock的是什么可能也不那么清楚。”不可否认,Python 的多线程机制确实存在自身的局限与痛点,但直接断言其“毫无意义”显然是言过其实了。很多人存在一个误区,以为多线程唯一的价值就在于压榨多核 CPU 的算力来提升程序运行速度。然而回顾计算机硬件发展史便会发现,世界上第一款多核 CPU——IBM 的 Power4 直到 2001 年才面世;在个人电脑领域,大家熟知的英特尔(Intel)和 AMD 更是直到 2005 年才推出首款多核 CPU。可多线程的概念早在上个世纪五六十年代就已经确立,倘若多线程只能用来利用多核,那在长达近半个世纪的时间里,多线程难道都是毫无意义的技术吗?
事实上,多线程从诞生之初所要解决的核心矛盾,就是在不显式切换任务的前提下,让 CPU 可以同时处理多个不同的任务,而这项价值在今天依然成立。其中最经典且被广泛讨论的应用场景就是“IO 瓶颈任务”(IO-bound tasks)。在单线程执行的常规程序中,一旦遇到文件读写、网络数据传输等较慢的 IO 操作,程序就必须停在原地干等,等待 IO 完成后才能执行后续代码。在这个漫长的等待窗口期内,CPU 实际上处于完全闲置的摸鱼状态。通过引入多线程机制,程序便能把 CPU 闲置的时间充分盘活,转而去执行其他运算与任务。
为了直观证明这一点,高天展示了一个简单的爬虫示例程序,其核心逻辑由 `run` 函数驱动,负责遍历词汇列表并下载对应的维基百科(Wikipedia)词条页面。高天对比了两种方案的耗时:第一种方案采用单线程顺序下载所有词汇的词条;第二种方案将词汇一分为二,交由两个线程并行下载。通过简易计时工具多次跑分测试,单线程执行耗时大约在 1 秒出头,而两个线程并发时耗时仅约 0.6 秒左右,性能近乎提升了一倍。这有力地印证了在面临 IO 瓶颈时,Python 多线程能实打实地带来可观的加速成效。
- **多核 CPU 发展里程碑**:IBM Power4(首款多核 CPU)发布于 2001 年;英特尔与 AMD 在 PC 端推出多核处理器是在 2005 年。 - **多线程概念诞生时代**:上世纪 50 至 60 年代,早于多核商用硬件近半个世纪。 - **IO 密集型测试效果**:维基百科爬虫抓取实验中,单线程耗时约 1 秒多,双线程耗时约 0.6 秒,多线程使处理速度几乎翻倍。
---
[02:51](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=171s) 协程的放权软肋与操作系统的抢占式调度
针对 IO 瓶颈任务,现代 Python 开发者更倾向于使用协程(`asyncio`)来处理。协程相较于操作系统线程更加轻量,且因为本身运行在单线程内,从根源上避免了多线程竞态条件(Race Condition)与数据冲突的困扰,因而近些年在网络传输等高并发应用中占据了越来越高的比例。但这并不代表协程的崛起宣判了多线程的死刑。高天指出,协程虽然具备诸多优秀特性,却拥有一个致命且无法回避的机制限制——它必须依靠代码显式地“放权”。在 Python 中,协程必须通过 `await` 关键字将 CPU 控制权主动交还给事件循环(Event Loop)。
如果整个系统内部全部都是频繁进行 IO 操作的微型任务,每个协程任务都会频繁让出控制权,协程确实表现优异;但现实场景往往错综复杂。例如系统中存在一个耗时很长的密集计算任务(并不着急出结果,可以慢慢算),同时又有大量需要低延迟响应的小型交互任务。此时单纯依赖协程的调度策略就会陷入死局:若不做额外干预,事件循环就会彻底陷在计算任务中无法自拔,导致低延迟任务超时卡死;若为了放权而在计算循环中人工硬塞一些无意义的放权点,既会让代码显得荒唐别扭,还要求开发者对自己代码的耗时阶段有极端精准的掌控。更糟糕的是,如果计算逻辑封装在第三方依赖库或他人的函数内部,开发者根本无权向其源码中插入放权逻辑。
```mermaid flowchart TD accTitle: 协程协作式调度与多线程抢占式调度对比 accDescr: 对比协程遇到长计算任务时无法响应低延迟任务,与多线程借助操作系统强制时钟切换保证公平调度的机制差异。
subgraph CoroutineModel["协程模型 (单线程协作式)"] CLong["长耗时计算任务"] -->|"未主动 await 放弃控制权"| Block["占用 Event Loop 线程卡死"] Block -.->|"无法执行"| CLatency["低延迟小任务 (饥饿超时)"] end
subgraph ThreadModel["多线程模型 (操作系统抢占式)"] TLong["线程 A: 长耗时计算任务"] TLatency["线程 B: 低延迟小任务"] OS["操作系统调度器 (时间片轮转/抢占)"] OS -->|"强制切换时间片"| TLong OS -->|"及时轮询保障响应"| TLatency end ```
这种困境正是多线程大显身手的舞台。高天生动地解释道:“由于操作系统的底层机制会自动帮你切换线程,你就不需要担心一个任务把CPU占死,然后其他人喝不到汤的情况。”在多线程模型下,操作系统的抢占式调度机制会强行按时间片分配执行权,即便某条线程陷入繁重的无休止计算,其他处理低延迟请求的线程依然能被系统唤醒并分得时间片。即使如今开发者在 `asyncio` 中使用 `run_in_executor` 来解决长任务卡死问题,其底层支撑本质上依旧依赖于多线程(或多进程)线程池。码农高天总结道,尽管由于 GIL 的存在,Python 无法像其他底层语言那样依靠多线程榨干多核 CPU 的计算峰值,但 Python 的多线程依然有其无法替代的价值。开发者应当搞懂技术的边界与长短板,因地制宜选择工具,而绝非人云亦云地把网络上的断章取义“奉为圭臬”。
- **协程调度痛点**:协作式调度(Cooperative Multitasking),任务必须通过 `await` 显式放权给 Event Loop;无法切入第三方不可控闭源函数强制中断。 - **多线程核心优势**:依赖操作系统底层的抢占式调度(Preemptive Multitasking),由系统时钟驱动线程上下文切换,防止长任务独占 CPU 造成线程饥饿。 - **异步兼顾方案的本质**:在 `asyncio` 中使用 `run_in_executor` 隔离长任务,底层机制依然依赖多线程池(ThreadPoolExecutor)或多进程池(ProcessPoolExecutor)。
## Notable quotes > [02:51](https://www.youtube.com/watch?v=1Bk3IpNsvIU&t=171s) 由于操作系统的底层机制会自动帮你切换线程,你就不需要担心一个任务把CPU占死,然后其他人喝不到汤的情况。 — 码农高天
## Mentioned 码农高天, Python, GIL, IBM, IBM Power4, 英特尔, AMD, 维基百科, asyncio
## Video description 这期视频聊一下多线程,很多人会说python的多线程没有存在价值,我们就来聊一下,这个说法到底对不对。
[Source](https://www.youtube.com/watch?v=1Bk3IpNsvIU)