线程如何在Python中工作,以及特定于Python线程的常见陷阱是什么?


85

我一直在努力地思考线程在Python中的工作方式,而且很难找到有关它们如何运行的良好信息。我可能只是缺少链接之类的东西,但似乎官方文档对该主题的了解不是很全面,而且我也找不到很好的文章。

据我所知,一次只能运行一个线程,活动线程每10条指令左右切换一次?

哪里有很好的解释,或者您可以提供一个解释?知道在Python中使用线程时遇到的常见问题也将非常高兴。

Answers:


50

是的,由于全局解释器锁定(GIL),一次只能运行一个线程。以下是一些有关此方面的见解的链接:

在最后一个链接中有一个有趣的报价:

让我解释一下这一切的含义。线程在同一虚拟机内运行,因此在同一物理机上运行。进程可以在同一台物理计算机上运行,​​也可以在另一台物理计算机上运行。如果您围绕线程构建应用程序,则您无济于事可访问多台计算机。因此,您可以扩展到一台计算机上的多个内核(随着时间的推移,内核数量会很多),但是要真正达到Web规模,则无论如何都需要解决多计算机问题。

如果要使用多核,则pyprocessing定义基于进程的API来进行真正的并行化。该PEP还包括了一些有趣的基准。


1
真的是对“ spanspan”报价的评论:Python线程肯定有效地将您限制为一个内核,即使该计算机有多个内核也是如此?多核可能会有好处,因为无需上下文切换就可以准备使用下一个线程,但是您的Python线程永远不能一次使用大于1的内核。
詹姆斯·布雷迪

2
正确,Python线程实际上仅限于一个内核,除非C模块与GIL很好地交互,并运行它自己的本机线程。
Arafangion

实际上,多核线程使有效,因为有大量的用户流失与检查,如果每个线程可以访问GIL。即使使用新的GIL,性能仍然会更差... dabeazaz.com/python/NewGIL.pdf
基本的

2
请注意,GIL注意事项并不适用于所有口译员。据我所知,IronPython和Jython都在没有GIL的情况下起作用,从而使它们的代码可以更有效地利用多处理器硬件。如Arafangion所述,如果不需要访问Python数据项的代码释放了锁,然后在返回之前再次获取了该锁,则CPython解释器也可以正常运行多线程。
holdenweb 2014年

是什么导致Python线程之间的上下文切换?它基于计时器中断吗?阻止还是特定的收益率电话?
CMCDragonkai '16

36

Python是一种相当容易使用的语言,但是有一些警告。您需要了解的最大信息是全局解释器锁定。这仅允许一个线程访问解释器。这意味着两件事:1)您很少在python中使用过lock语句; 2)如果您想利用多处理器系统,则必须使用单独的进程。编辑:我还应该指出,如果您也想绕开GIL,可以将一些代码放在C / C ++中。

因此,您需要重新考虑为什么要使用线程。如果要并行化应用程序以利用双核体系结构,则需要考虑将应用程序分解为多个进程。

如果要提高响应速度,则应考虑使用线程。但是,还有其他选择,即微线程。您还应该研究一些框架:


@JS-固定。该列表已经过时了。
杰森·贝克

对于我来说,要使用多核系统需要多个进程-随之而来的所有开销,这对我来说真是不对。我们有一些带有32个逻辑核心的服务器-所以我需要32个进程来有效地使用它们?疯狂
基本的

@Basic-最近几天启动进程与启动线程的开销是最小的。我想如果我们每秒谈论成千上万的查询,您可能会开始发现问题,但是首先我会质疑对于如此繁忙的服务选择Python的问题。
杰森·贝克

20

下面是一个基本的线程示例。它将产生20个线程;每个线程将输出其线程号。运行它,并观察其打印顺序。

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

正如您所暗示的,Python线程是通过时间切片实现的。这就是他们获得“平行”效果的方式。

在我的示例中,我的Foo类扩展了线程,然后实现了run方法,这是您要在线程中运行的代码所在的位置。要启动您start()在线程对象上调用的线程,它将自动调用该run方法...

当然,这只是最基本的知识。您最终将想要了解有关信号量,互斥量和线程同步和消息传递的锁的信息。


10

如果单个工作人员正在执行I / O绑定操作,请在python中使用线程。如果要在计算机上跨多个内核扩展,请为python找到一个好的IPC框架,或者选择其他语言。


4

注意: 无论我在哪里提到,thread我的意思是明确声明之前专门在python中使用线程

如果您来自C/C++后台,线程在python中的工作方式会有所不同。在python中,给定时间只能有一个线程处于运行状态,这意味着python中的线程无法真正利用多个处理内核的功能,因为根据设计,线程不可能在多个内核上并行运行。

由于python中的内存管理不是线程安全的,因此每个线程都需要对python解释器中的数据结构进行独占访问。此独占访问是通过称为(全局解释器锁)的机制获取的。GIL

Why does python use GIL?

为了防止多个线程同时访问解释器状态并破坏解释器状态。

这个想法是,每当执行一个线程(即使它是主线程)时,都会获取一个GIL,并且在某个预定义的时间间隔后,当前线程会释放该GIL,而其他某个线程(如果有)会重新获取该GIL。

Why not simply remove GIL?

删除GIL并不是不可能的,只是这样做的目的是,我们最终在解释器中放置了多个锁以便对访问进行序列化,这甚至使单线程应用程序的性能降低。

因此删除GIL的成本是通过降低单线程应用程序的性能来弥补的,这是从未希望的。

So when does thread switching occurs in python?

GIL释放时会发生线程切换。那么GIL何时释放?有两种情况需要考虑。

如果线程正在执行CPU绑定操作(Ex图像处理)。

在旧版本的python中,线程切换通常是在固定数量的python指令之后发生的,默认情况下设置为。100由于执行一条指令所花费的时间,决定何时应该进行切换并不是一个很好的策略从几毫秒甚至到一秒都可能非常疯狂100

在新版本中,不是使用指令计数作为度量标准来切换线程,而是使用了可配置的时间间隔。默认的切换间隔是5毫秒。您可以使用来获取当前的切换间隔sys.getswitchinterval()。可以使用更改sys.setswitchinterval()

如果线程正在执行某些IO绑定操作(例如文件系统访问权限或
网络IO)

每当线程等待某些IO操作完成时,就会释放GIL。

Which thread to switch to next?

解释器没有自己的调度程序。在间隔结束时调度哪个线程是操作系统的决定。。


3

GIL的一种简单解决方案是多处理模块。它可以用作线程模块的替代品,但使用多个解释器进程而不是线程。因此,与简单线程相比,简单事务的开销要大一些,但是如果需要,它可以为您带来真正的并行化的优势。它还可以轻松扩展到多个物理计算机。

如果您需要真正大规模的并行化,那么我将进一步研究,但是如果您只想扩展到一台计算机或几个不同内核的所有内核,而无需花很多时间去实现一个更全面的框架,那将是您的不二之选。 。


2

尝试记住,GIL设置为经常轮询,以显示多个任务的外观。可以对这个设置进行微调,但是我建议应该进行一些工作,线程正在执行,或者很多上下文切换将导致问题。

我可能会建议在处理器上使用多个父级,并尝试将类似作业保持在同一内核上。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.