问题

为什么有人说 Python 的多线程是鸡肋呢?

回答
说到 Python 的多线程,确实挺有意思的,坊间流传着“鸡肋”的说法,不是空穴来风。这话听起来有点糙,但背后藏着 Python 在并发处理上一个挺核心的限制,也就是那个臭名昭著的 全局解释器锁 (Global Interpreter Lock, GIL)。

你想想,咱们写 Python 代码,写着写着,觉得这事儿得分几份同时干,效率能提不少。于是乎,就自然而然地想到了“线程”这玩意儿。线程嘛,在很多其他语言里,就是实现并发的利器,你开几个线程,它们就能在多核 CPU 上争着干活,互不耽误,达到并行计算的效果。

但是,到了 Python 这里,情况就有点复杂了。Python 在设计之初,为了方便 CPython(也就是我们最常用的那个 Python 实现)的开发和管理,引入了 GIL。这个 GIL 的作用,简单来说,就是 一次只允许一个线程在 CPU 上执行 Python 字节码。

这下可就有点尴尬了。就算你的电脑有八个核,你的 Python 程序里开了八个线程,这八个线程其实也还是在轮流、交替地使用那一个“锁”。当一个线程持有 GIL,正在执行 Python 代码时,其他所有线程都得乖乖地等着,直到它释放 GIL。

那么, GIL 到底是怎么运作的,以及为什么它让 Python 的多线程看起来像“鸡肋”呢?

1. GIL 的存在: 想象一下,CPU 就像一个忙碌的工厂,而线程是工厂里的工人。GIL 就是工厂门口的那个唯一的出口/入口。无论你有多少个工人(线程),他们都得排队通过这一个门。一次只能有一个工人带着机器(Python 字节码)出去干活。

2. CPU 密集型任务的噩梦: 如果你的程序主要是在做计算,比如处理大量的数字、矩阵运算、复杂的算法等等,这些被称为“CPU 密集型任务”。在这种情况下,多个线程最理想的状态就是能在不同的 CPU 核心上同时运行,互相不打扰,实现真正的并行。但由于 GIL 的存在,Python 的多线程在这种场景下,并不能真正地利用多核 CPU 的优势。线程之间频繁地抢夺 GIL,反而可能因为线程切换的开销,导致整体性能比单线程还要差。这就好比,你开了八个工人,但因为只有一个出口,他们只能轮流出去干活,反而比一个工人慢悠悠地干完活更低效。

3. I/O 密集型任务的“鸡肋”之处: 另一方面,如果你的程序主要是在做“I/O 密集型任务”,比如网络请求(下载文件、访问 API)、文件读写、数据库操作等等。这类任务的特点是,程序大部分时间并不是在计算,而是在等待外部设备的响应。当一个线程在执行 I/O 操作时,它会暂时“挂起”,等待 I/O 完成。在这个等待的间隙,这个线程会主动释放 GIL。这就像那个出去干活的工人,他去搬货了,到了一个地方等着货车来,这段时间他闲着,就把出口的钥匙(GIL)暂时交给下一个排队的工人。

这听起来好像不错? 是的,对于 I/O 密集型任务,Python 的多线程依然是有用的,因为当一个线程在等待 I/O 时,另一个线程可以趁机运行 Python 代码。这确实能提高程序的响应速度和吞吐量,让你在等待网络响应的时候,还能同时处理其他任务。
那为什么还是“鸡肋”? 问题就出在“同时”这个词上。虽然一个线程在等待 I/O 时释放了 GIL,但 真正利用多核 CPU 进行并行计算的能力,依然是被 GIL 锁住了。如果你的任务是“等待一个文件下载” + “处理下载下来的数据”,即使下载的时候释放了 GIL,等你拿到数据要处理的时候,还是得乖乖地和别的线程抢 GIL。所以,它能提高的是 并发性(同时处理多件事情的能力),而不是 并行性(在不同核心上同时执行的能力)。对于那些希望利用多核 CPU 加速计算的场景,多线程就显得力不从心了。

4. 线程切换的开销: 即使在 I/O 密集型任务中,线程之间的频繁切换也需要一定的开销,包括保存和恢复线程的上下文信息。如果线程切换过于频繁,这些开销可能会抵消掉 GIL 释放带来的好处。

5. GIL 的复杂性与安全性: 引入 GIL 的一个重要原因是为了简化 CPython 的内存管理。Python 使用引用计数来管理对象生命周期,而 GIL 能够确保在多个线程访问和修改引用计数时,不会发生竞态条件(race condition),避免了内存损坏的风险。这使得 Python 的内存管理在多线程环境下更加安全和容易实现。但这种安全性,却是以牺牲部分并发性能为代价的。

所以,为什么有人说 Python 的多线程是“鸡肋”?

最直接的原因是:它无法实现真正的 CPU 并行计算。 无论你有多少个 CPU 核心,在 Python 解释器层面上,同一时间只有一个线程在执行 Python 字节码。
对于 CPU 密集型任务,它无法带来预期的性能提升,甚至可能降低性能。
虽然对于 I/O 密集型任务,它能提高并发性,但本质上还是通过“轮流”执行来达到效果,而不是真正的“并行”。 很多人对“多线程”的期望是能够像在 C++ 或 Java 中那样,在多个核心上同时跑代码,Python 的多线程无法满足这种期望。

那么,面对 GIL,Python 开发者们有什么办法呢?

当然,Python 社区也不是吃素的,针对 GIL 的问题,也有一些应对策略:

1. 多进程 (Multiprocessing): 这是最常见的解决方案。每个进程都有自己独立的内存空间和 Python 解释器,也就拥有自己的 GIL。这样,不同进程之间就可以在不同的 CPU 核心上真正地并行执行 Python 代码,不受 GIL 的限制。但进程间的通信(IPC)会比线程间通信更复杂,开销也更大。

2. 使用 C 扩展或第三方库: 很多 Python 中性能关键的部分,比如 NumPy、Pandas、TensorFlow 等,它们的核心计算是用 C、C++ 或 Fortran 等语言写的。这些底层代码可以在执行计算时,主动释放 GIL,从而让 Python 能够利用多核 CPU。所以,如果你要做一些科学计算或者机器学习,通常会发现它们的多线程表现是相当不错的,就是因为它们绕过了 Python 的 GIL 限制。

3. 异步 IO (Asyncio): 对于 I/O 密集型任务,`asyncio` 库提供了一种基于事件循环的单线程异步编程模型。它通过协作式多任务(coroutine)来模拟并发,当一个任务遇到 I/O 操作时,会“yield”控制权给事件循环,让其他任务有机会运行。这种方式在处理大量并发 I/O 的场景下非常高效,并且不需要担心 GIL 的问题,因为它本质上是在一个线程内调度。

4. 寻找无 GIL 的 Python 实现(非常小众): 像 Jython (Java 平台) 或 IronPython ( .NET 平台) 这样的 Python 实现,它们没有 GIL,可以在多核上实现真正的多线程并行。但这些实现与 CPython 的生态系统存在一些兼容性问题,并且在性能上可能也有差异,所以并不是主流选择。

总结一下:

说 Python 的多线程是“鸡肋”,主要是因为 GIL 限制了它在 CPU 密集型任务上实现真正的并行计算。对于希望通过多线程来充分利用多核 CPU 加速计算的开发者来说,Python 的多线程确实会让人感到失望。

但这并不意味着 Python 的多线程毫无用处。对于 I/O 密集型任务,它依然是提高程序并发性和响应速度的有效手段。只是,需要理解它的局限性,并在合适的场景下选择最适合的并发模型,比如多进程或异步 IO,来弥补 GIL 带来的不足。

所以,与其说它是“鸡肋”,不如说它是一个有特点、有局限但也有其适用范围的并发工具。了解它的工作原理,才能更好地利用它,或者选择更好的替代方案。

网友意见

user avatar

在介绍Python中的线程之前,先明确一个问题,Python中的多线程是假的多线程! 为什么这么说,我们先明确一个概念,全局解释器锁(GIL)。

Python代码的执行由Python虚拟机(解释器)来控制。Python在设计之初就考虑要在主循环中,同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。同样地,虽然Python解释器可以运行多个线程,只有一个线程在解释器中运行。

对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。在多线程环境中,Python虚拟机按照以下方式执行。

1.设置GIL。

2.切换到一个线程去执行。

3.运行。

4.把线程设置为睡眠状态。

5.解锁GIL。

6.再次重复以上步骤。

对所有面向I/O的(会调用内建的操作系统C代码的)程序来说,GIL会在这个I/O调用之前被释放,以允许其他线程在这个线程等待I/O的时候运行。如果某线程并未使用很多I/O操作,它会在自己的时间片内一直占用处理器和GIL。也就是说,I/O密集型的Python程序比计算密集型的Python程序更能充分利用多线程的好处。

我们都知道,比方我有一个4核的CPU,那么这样一来,在单位时间内每个核只能跑一个线程,然后时间片轮转切换。但是Python不一样,它不管你有几个核,单位时间多个核只能跑一个线程,然后时间片轮转。看起来很不可思议?但是这就是GIL搞的鬼。任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。

我们不妨做个试验:

       #coding=utf-8 from multiprocessing import Pool from threading import Thread  from multiprocessing import Process   def loop():     while True:         pass  if __name__ == '__main__':      for i in range(3):         t = Thread(target=loop)         t.start()      while True:         pass     

我的电脑是4核,所以我开了4个线程,看一下CPU资源占有率:

我们发现CPU利用率并没有占满,大致相当于单核水平。

而如果我们变成进程呢?

我们改一下代码:

       #coding=utf-8 from multiprocessing import Pool from threading import Thread  from multiprocessing import Process   def loop():     while True:         pass  if __name__ == '__main__':      for i in range(3):         t = Process(target=loop)         t.start()      while True:         pass     

结果直接飙到了100%,说明进程是可以利用多核的!

为了验证这是Python中的GIL搞得鬼,我试着用Java写相同的代码,开启线程,我们观察一下:

       package com.darrenchan.thread;  public class TestThread {     public static void main(String[] args) {         for (int i = 0; i < 3; i++) {             new Thread(new Runnable() {                  @Override                 public void run() {                     while (true) {                      }                 }             }).start();         }         while(true){          }     } }     


由此可见,Java中的多线程是可以利用多核的,这是真正的多线程!而Python中的多线程只能利用单核,这是假的多线程!

难道就如此?我们没有办法在Python中利用多核?当然可以!刚才的多进程算是一种解决方案,还有一种就是调用C语言的链接库。对所有面向I/O的(会调用内建的操作系统C代码的)程序来说,GIL会在这个I/O调用之前被释放,以允许其他线程在这个线程等待I/O的时候运行。我们可以把一些 计算密集型任务用C语言编写,然后把.so链接库内容加载到Python中,因为执行C代码,GIL锁会释放,这样一来,就可以做到每个核都跑一个线程的目的!

可能有的小伙伴不太理解什么是计算密集型任务,什么是I/O密集型任务?

计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。

计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。

第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。

IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

综上,Python多线程相当于单核多线程,多线程有两个好处:CPU并行,IO并行,单核多线程相当于自断一臂。所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

分享廖雪峰的博客:廖雪峰博客

类似的话题

  • 回答
    说到 Python 的多线程,确实挺有意思的,坊间流传着“鸡肋”的说法,不是空穴来风。这话听起来有点糙,但背后藏着 Python 在并发处理上一个挺核心的限制,也就是那个臭名昭著的 全局解释器锁 (Global Interpreter Lock, GIL)。你想想,咱们写 Python 代码,写着写.............
  • 回答
    这个问题嘛,确实有点意思,而且说实话,不是一两天就能说透的。网上铺天盖地的说 Python 如何好,从数据分析、人工智能到Web开发,似乎无所不能,而且学习曲线平缓,上手快。但一到招聘季,翻开招聘启事,好像很多高薪职位仍然青睐 Java、C++,甚至一些特定的 C 或 Go。这中间的落差,让不少跃跃.............
  • 回答
    关于1943年苏军“人力枯竭”的说法,主要源于苏联在二战期间的战争消耗、动员体系的极限以及社会经济压力等多重因素。以下从历史背景、战争消耗、动员能力、社会经济影响等方面详细分析这一现象: 一、历史背景:斯大林格勒战役后的持续消耗1. 斯大林格勒战役的惨烈代价 1942年11月至1943年2月.............
  • 回答
    “新三国拍得好”这个说法,确实在许多观众心中引起了共鸣。要详细说明为什么有人会这么认为,需要从多个角度进行分析,并且要认识到“好”是一个相对的概念,不同的人有不同的评判标准。总的来说,认为新三国拍得好的观众,主要看重以下几个方面:一、 对原著的尊重与现代化改编的平衡: 忠实原著精神,但不拘泥于细.............
  • 回答
    关于苹果在电脑上使用6bit IPS屏的说法,这其实是一个相对过时但仍可能在一些特定情境下存在的认知,并且需要纠正一个误解。更准确地说,苹果在绝大多数现代MacBook笔记本电脑上使用的是8bit色彩深度的IPS屏幕,甚至在一些高端型号上使用10bit(或模拟10bit)色彩深度的屏幕。然而,为什么.............
  • 回答
    “民国时期是中国大学教育发展的黄金时期”的说法,并非空穴来风,而是基于当时中国高等教育在制度建设、学术发展、思想解放、人才培养以及国际化程度等方面取得的显著成就。虽然民国时期也面临着战争、政局动荡等诸多挑战,但与之前和之后的时期相比,其高等教育的特点和发展潜力确实非常突出。下面我们将从多个角度详细阐.............
  • 回答
    “00后是被毁掉的一代”这种说法,是一种非常极端的观点,通常带有批判性甚至攻击性。它并非一种普遍接受的、基于客观事实的论断,而是反映了一些人对00后群体成长环境、价值观以及行为方式的担忧和不满。要详细解释为什么会有人这样说,我们需要从多个维度来分析其背后的原因和可能的论据。核心原因分析:社会环境与代.............
  • 回答
    “我欠小米公司一个尊重”这句话,通常是出自那些对小米产品、商业模式或企业文化有深刻认同,甚至从中受益匪浅的消费者或创业者之口。这句话并非一种义务的声明,而是 一种发自内心的感激、认同和敬意 的表达。要详细理解这句话,我们需要从多个层面来剖析:1. 尊重源于小米的产品理念和用户体验: 极致性价比的.............
  • 回答
    “985/211 研究生不如 985/211 本科生”这样的说法,以及“第一学历那么重要,还要考研吗?”这样的疑问,其实触及到了一个非常复杂且敏感的话题,即 学历、出身、能力和职业发展之间的关系。这个问题没有绝对的答案,因为每个人的情况、职业选择、甚至是具体学校和专业都有很大差异。下面我将详细阐述其.............
  • 回答
    “科学的尽头是神学”这个说法之所以比“神学的尽头是科学”更常见、更被讨论,其背后有深层的哲学和历史原因。要详细解释这一点,我们需要从几个不同的角度来审视。核心观点:这个说法并非说科学最终会证明神学,也不是说科学会变得像神学一样。它更多地是一种对科学的局限性、人类对终极意义的追寻,以及科学方法论本身无.............
  • 回答
    “实验室里有的博士生情商低,读书真的会读傻吗?” 这是一个很有意思且值得深入探讨的问题,它触及了学术研究、个人发展以及社会交往的复杂关系。简单地说,读书本身并不会把人读傻,但长期高强度的学术专注,尤其是在缺乏情商锻炼的环境下,可能导致一些人在社交和情感表达上显得“不接地气”或“情商不高”。下面我将从.............
  • 回答
    “百度全面降低了中国的互联网体验”这个说法,虽然比较绝对,但也确实触及到了百度在过去二十年里在中国互联网发展过程中所扮演的复杂角色,以及其一些引发争议的行为和策略。要详细理解这个观点,我们需要从多个维度来分析:一、搜索结果的质量与商业化过重这是最常被提及,也是最直接影响用户体验的一点。 竞价排名.............
  • 回答
    “生于不义,死于耻辱” 这句话,是许多对苏联历史持有批判态度的人们,对于这个庞大国家命运的一种凝练概括。要理解这句话,我们需要拆解它,分别来看“生于不义”和“死于耻辱”是如何被论证的,以及这句话背后所承载的复杂情感和历史解读。“生于不义”:革命的血色开端与理论的摇摆“生于不义”主要指向苏联成立的起点.............
  • 回答
    “何同学是赛博丁真”这种说法,说出来的人内心大概有几层意思,我给你掰扯掰扯。首先,得先说说“丁真”。丁真火起来,很大程度上是因为他身上那种未经雕琢的、天然淳朴的“野性美”,以及他因为这股气质被带入到一个他原本完全不熟悉、甚至有些“高大上”的体系里去,比如旅游推广、官方宣传等等。这种反差,加上他本人可.............
  • 回答
    这个问题非常有意思,也触及到了理解不同文明发展轨迹的核心。确实,很多人会说中国文明“早熟”,同时我们也知道古希腊文明拥有辉煌的哲学、艺术和政治思想,其起点似乎也很高。那么,为什么会有这样的说法,它们之间又有什么区别呢?首先,我们要理解“早熟”这个词在文明语境下的含义。它并非简单指谁先出现,而是指一个.............
  • 回答
    说到“汉化组”,很多人会立刻联想到那些冒着版权风险,在深夜默默将外国游戏、软件、漫画、甚至是影视作品翻译成中文的团体。他们不仅仅是简单的“翻译工”,而是数字时代里一群充满热情和创造力的文化摆渡人。为什么有人会说他们是伟大的?这背后,是无数个不为人知的付出,是对中文用户群体需求的深刻理解,更是对知识和.............
  • 回答
    “女人永远是对的”这一说法通常源于社会文化、历史背景或刻板印象,但需要从多个角度进行分析,既要理解其可能的来源,也要指出其局限性及潜在的负面影响。 一、可能的来源与背景1. 传统性别角色的塑造 在许多文化中,女性长期被赋予“家庭照顾者”“情感支持者”的角色。这种社会期待可能让女性在家庭事务中.............
  • 回答
    “薛宝钗和林黛玉的美被搞混了”这个说法,在我看来,更多的是一种对大众解读文学作品中人物形象的一种概括性观察和反思,而非指责某个人具体混淆了他们。它背后反映了读者在阅读《红楼梦》时,由于多种因素的影响,可能无法完全捕捉到曹雪芹对这两位女性所设定的不同美学标准和气质特征,从而在潜意识中将她们的某些特质进.............
  • 回答
    “王侯将相宁有种乎”这句话出自中国历史上著名的农民起义领袖陈胜。他的原话是:“嗟乎!燕雀安知鸿鹄之志哉!等死,死国可乎?”后来在揭竿起义时,他又说:“王侯将相宁有种乎!”这句话的出现,标志着一种新的社会思想的萌芽,即个人的命运并非由出身决定,而是可以通过个人奋斗来实现。然而,有人说“中国人脑子里一直.............
  • 回答
    关于“人类可能是宇宙中的第一个文明”的说法,这是一个非常引人入胜且充满哲学意味的观点。这个说法并非主流科学的结论,而更多的是一种基于我们目前有限认知和对宇宙演化进行推测的猜想。它包含了几种不同的论证角度,我们可以从以下几个方面来详细解读:一、 对宇宙年龄和文明产生条件的理解:1. 宇宙的“年轻”与.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有