码农高天反驳Python多线程无用论 指抢占调度仍防长任务阻塞

【python】听说Python的多线程是假的?它真的没有存在的价值么? — 码农高天

码农高天

即使协程在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)

中文简报

高天反驳Python多线程无用论:抢占式调度仍可防长任务阻塞 即使协程在IO场景更轻量,多线程仍依赖操作系统抢占式调度防止长计算任务独占CPU,是不可替代的保底方案。 高天从计算机硬件发展史切入,指出多线程概念远早于多核 CPU 面世,其核心使命是在不显式切换任务的前提下盘活 CPU 算力,并通过维基百科爬虫实验证明多线程在 IO 瓶颈任务下依然能实现接近翻倍的加速。随后他对比了多线程与现代协程(`asyncio`)的区别,指出协程高度依赖 `await` 显式协作放权,一旦遭遇长耗时计算或不可控的第三方阻塞函数就会卡死事件循环。相比之下,多线程依托操作系统的抢占式调度,能强制按时间片轮转分配 CPU,有效防止低延迟交互任务被饿死,在现代异步体系底层依然扮演着不可替代的角色。 针对“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)

Reading summary

码农高天指出,认为“Python多线程因全局解释器锁(GIL)而毫无意义”属于认知误区。多线程技术最初并非为了压榨多核CPU算力而生,在应对IO瓶颈以及借助操作系统实现抢占式调度方面,它依然具备协程无法完全替代的工程价值。 突破等待瓶颈:多线程的历史渊源与IO加速 (0:00) 很多人误以为多线程的唯一作用是利用多核算力,但计算机发展史表明,多线程概念早在20世纪50至60年代便已诞生,而首款多核CPU(IBM Power4)直到2001年才问世,PC端的英特尔与AMD多核处理器更是推迟至2005年。多线程自诞生起要解决的核心矛盾,就是在程序遇到文件读写或网络传输等慢速IO操作时,让CPU不必闲置等待,转而去处理其他任务。在维基百科词条抓取实验中,单线程顺序下载耗时约1秒多,而两个线程并发下载耗时仅约0.6秒,证明多线程在IO密集型任务中能带来接近翻倍的加速效果。 协作式与抢占式:多线程对长任务独占的解法 (2:51) 现代Python常用协程(asyncio)处理网络并发,协程轻量且无数据竞态问题,但它采用协作式调度,必须依赖代码通过 await 显式放权给事件循环。当系统中混合了长耗时计算任务,或者调用了无法修改源码插入放权逻辑的第三方库函数时,协程事件循环会被彻底阻塞,导致需要低延迟响应的交互任务饥饿超时。多线程模型则依托操作系统的抢占式调度机制,由系统通过时间片轮转强制切换执行权,确保长计算任务不会独占CPU。即便在 asyncio 中使用 run_in_executor 隔离耗时任务,其底层实质上依然依赖多线程池或多进程池的支持。 码农高天总结认为,尽管GIL限制了Python依靠多线程榨干多核CPU的极限计算性能,但多线程在规避IO等待与保障调度公平性上仍具有不可替代的作用。开发者应当清晰认知不同并发工具的技术边界与机制差异,因地制宜进行选型,而非脱离场景全盘否定多线程。

Source quotes

[2:51] 码农高天: 由于操作系统的底层机制会自动帮你切换线程,你就不需要担心一个任务把CPU占死,然后其他人喝不到汤的情况。

Original transcript

[0:00] 如果你是python的使用者 这些话你肯定听到过无数次Tight-down的多线程是假的Python的多线程是鸡肋拍档短线程没有意义说这些话的人呢,其中有挺大一部分,也无非就是知道一个gill甚至Gill到底lock的是什么可能也不那么清楚Python多线程确实有它的问题,但是说多线程没有意义显然就养过其实了今天呢 我们就来聊聊Python的多线程能干啥很多人可能觉得多线程唯一的价值就是利用多核CPU提升程序的运行速度但是实际上第一个多核CPU IBM的Power4出现在2001年而在个人电脑上我们更熟悉的英特尔、AMD是在2005年才推出的他们的第一款多数CPU可是多线程这个概念在上个世纪五六十年代就已经出现了那这中间接近半个世纪的时间里多限程都是没有意义的吗多线程出现之初啊,解决的核心问题是,在不显示的切换任务的情况下,让CPU可以同时处理若干个任务而这个价值直至今日依然存在这里主要有两件事 值得一提第一个是我们相对讨论的更多的叫做Io瓶颈任务什么意思呢?就是在单线程的情况下,我们正常写程序,对吧?在遇到比较慢的IO任务,比如说文件读取啊,网络传输之类的事情的时候,是需要原地干等着的,对吧?要等这个爱欧任务完成了才能接着做下面的事情在这个时间 CPU 实际上就在闲着通过多线程 我们可以把这段闲着的时间给利用起来 干点别的我们给大家举个例子啊大家看这边我写了一个非常简单的爬虫程序它的核心代码就在 run 函数里面对于你给他的每一个单词下载他的wiki page这里我们简单的做了两个方案第一个方案是把所有的单词用一个线程来下载第二个方案是把这些单词平均分成两份,然后用两个线程来下载下面让我们就做了一个非常简陋的计时程序啊算一算需要多长时间多运行几次我们可以看到单线程的时候差不多是一秒多一点而两个线程大概是0.6秒左右算下来差不多两个县城比一个县城快了一倍啊这个呢就是在IO瓶颈下多线程对程序的提速效果那当然,对于IO平均的任务,我们现在经常可以使用写程,也就是asyncIO来完成鞋程相对现场更轻而且由于是单线程没有竞争冒险的问题所以在网络传输的应用里面使用的比例越来越高了那邪城的出现就宣判了多县城的死刑吗也不是吧尽管鞋长有着很多优质的特性但是他也有一个无法回避的问题就是必须要显示的放全在python中就是必须要通过wait的形式去把控制权交还给event loop在有些情况下比如你所有的程序本质上都是大量的IO操作的时候啊这不是个问题因为每一个斜程的task都会频繁的放权但是有些情况下单纯的邪程就不太好用比如你有一个大量计算的任务,但是不着急,可以慢慢做有很多小的任务需要在低延迟下解决这个时候怎么处理邪程的调度就是一个麻烦的事情如果你不做任何处理你的邪程就会陷入到计算任务中出不来没法及时的处理那些需要低延迟的小任务可如果你在计算任务中人工安置一些放权点又会让你这个任务看起来很奇怪更何况这还需要你对这个任务的消耗时间足够了解不仅如此,如果你调用的是别人的函数,你是没法在别人的函数里面放全的那多线程的存在就可以比较好的解决这种问题由于操作系统的底层机制会自动帮你切换线程你就不需要担心一个任务把CPU占死,然后其他人喝不到汤的情况当然了,你现在可以在AsyncIO里面用这个run in executor 来解决这个问题啊但是我们要知道这个解决方案的背后啊它的本质啊它就是多现成当然也有可能是多进程说了这么多呢就想告诉大家尽管由于Gill的存在Python没有办法像其他的一些语言一样通过多线程来利用多核提升计算 [5:00] 的程序运行速度但是拍档中的多线程也完全是有其存在价值的你需要去了解他能做什么不能做什么有什么优势有什么劣势然后在不同的场景下使用合适的工具而不是人云亦云 抱着网上听来的只言片语风味归腻好的 这期视频就到这里 喜欢的话记得点一下关注 别忘了一键三连哦!字幕志愿者 杨茜茜