88

我一直在试图了解线程在 Python 中是如何工作的,而且很难找到关于它们如何操作的好信息。我可能只是错过了一个链接或其他东西,但似乎官方文档在这个主题上不是很详尽,而且我还没有找到一个好的文章。

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

哪里有好的解释,或者你能提供一个吗?了解在 Python 中使用线程时遇到的常见问题也是非常好的。

4

7 回答 7

53

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

在最后一个链接中引用了一个有趣的引述:

让我解释一下这一切意味着什么。线程在同一虚拟机内运行,因此在同一物理机上运行。进程可以在同一台物理机上运行,​​也可以在另一台物理机上运行。如果您围绕线程构建您的应用程序,那么您就没有做任何事情来访问多台机器。因此,您可以扩展到单台机器上的尽可能多的核心(随着时间的推移,这将是相当多的),但要真正达到网络规模,您无论如何都需要解决多机问题。

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

于 2008-08-28T00:19:50.320 回答
36

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

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

如果你想提高响应能力,你应该考虑使用线程。不过,还有其他选择,即微线程。您还应该研究一些框架:

于 2008-08-28T00:00:18.167 回答
21

下面是一个基本的线程示例。它将产生 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方法...

当然,这只是最基本的。您最终会想要了解信号量、互斥体和用于线程同步和消息传递的锁。

于 2008-08-27T23:52:59.743 回答
14

注意: 在我提到的任何地方,我都thread指的是python 中的线程,直到明确说明。

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

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

Why does python use GIL?

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

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

Why not simply remove GIL?

并不是说它不可能删除 GIL,只是在这样做的过程中,我们最终在解释器中放置了多个锁以序列化访问,这使得即使是单线程应用程序的性能也降低了。

因此,删除 GIL 的成本可以通过降低单线程应用程序的性能来得到回报,而这并不是我们所希望的。

So when does thread switching occurs in python?

线程切换发生在 GIL 释放的时候。那么 GIL 什么时候释放呢?有两种情况需要考虑。

如果线程正在执行 CPU Bound 操作(例如图像处理)。

在旧版本的 python 中,线程切换通常在固定100数量的 python 指令之后发生。默认设置为可以非常疯狂地从毫秒到甚至一秒。因此,在每条指令之后释放 GIL,100而不管它们执行的时间是一个糟糕的策略。

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

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

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

Which thread to switch to next?

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

于 2018-03-30T11:47:11.123 回答
10

如果单个工作人员正在执行 I/O 绑定操作,请在 python 中使用线程。如果你想在一台机器上跨多个内核进行扩展,要么为 python 找到一个好的IPC框架,要么选择一种不同的语言。

于 2008-08-28T02:34:18.967 回答
3

GIL 的一种简单解决方案是多处理模块。它可以用作线程模块的替代品,但使用多个解释器进程而不是线程。正因为如此,对于简单的事情来说,它比普通线程的开销要多一些,但如果你需要它,它会给你带来真正并行化的优势。它还可以轻松扩展到多台物理机器。

如果您需要真正的大规模并行化,我会看的更远,但是如果您只想扩展到一台计算机的所有内核或几个不同的内核,而无需进行所有工作来实现更全面的框架,那么这就是为您准备的.

于 2014-02-06T16:19:03.467 回答
2

请记住,GIL 设置为每隔一段时间进行一次轮询,以便显示多个任务的外观。这个设置可以微调,但我建议应该有线程正在做的工作,否则大量的上下文切换会导致问题。

我什至会建议多个父母使用处理器,并尝试在同一个内核上保留类似的工作。

于 2009-07-28T22:40:05.843 回答